//-------------------------------------------------------------------------------------------------
// Copyright (c) Bradford W. Mott and Flare Contributors
// North Carolina State University, Department of Computer Science
// The IntelliMedia Group
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
// SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
// OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//-------------------------------------------------------------------------------------------------

using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEngine;
using Ionic.Zlib;

using Flare.Geom;
using Flare.Sys;
using Flare.Text;
using Flare.Script;
using Flare.Media;

namespace Flare.Display
{
    /// <summary>
    /// The Loader class is used to parse SWF files. The load method reads the SWF content
    /// and attaches it to the content property of the loader. The current implementation of
    /// loading is a synchronous operation.
    /// 
    /// Since a loader can only have one child (the display object it loads) the following
    /// methods throw an exception: AddChild(), AddChildAt(), RemoveChild(), RemoveChildAt(),
    /// and SetChildIndex(). 
    /// </summary>
    public class Loader : DisplayObjectContainer
    {
        private byte[] m_jpegTables;
        private bool m_processedDefineSceneAndFrameLabelDataTag = false;

        /// <summary>
        /// Get the root content of the loaded SWF file.
        /// </summary>
        public DisplayObject content { get; private set; }

        /// <summary>
        /// Get the LoaderInfo object for the object being loaded.
        /// </summary>
        public LoaderInfo contentLoaderInfo { get; private set; }
     
        /// <summary>
        /// Gets the LoaderContext for the content being loaded.
        /// </summary>
        internal LoaderContext context {
            get
            {
                return (this.contentLoaderInfo != null) ? this.contentLoaderInfo.context : null;
            }
        }

        /// <summary>
        /// Initializes a new instance of the Loader class.
        /// </summary>
        public Loader()
        {
            this.m_jpegTables = null;
            this.m_processedDefineSceneAndFrameLabelDataTag = false;
            this.content = null;
            this.contentLoaderInfo = null;
        }

        /// <summary>
        /// Not supported by the Loader class.
        /// </summary>
        public override object Clone()
        {
            throw new global::System.NotSupportedException("Cloning not supported.");
        }

        /// <summary>
        /// Load the SWF file contents from the specified byte array using the given loader
        /// context. If no loader context is given then a new context using a child application
        /// domain of the current application domain is used.
        /// </summary>
        public void Load(byte[] bytes, LoaderContext context = null)
        {
            if (NativeWindow.windowActiveForScripts == null)
            {
                // NativeWindow.windowActiveForScripts should never be null
                throw new global::System.InvalidOperationException(
                    "ActivateForScripts must be called on a NativeWindow prior to loading. " +
                    "Creating a NativeWindow will implicitly do this.");
            }

            this.m_jpegTables = null;
            this.m_processedDefineSceneAndFrameLabelDataTag = false;
            this.content = null;

            // Create the loader info object and link it back to this loader and setup context
            this.contentLoaderInfo = new LoaderInfo();
            this.contentLoaderInfo.loader = this;
            this.contentLoaderInfo.context = (context != null) ? context : new LoaderContext();

            // Parse the SWF file
            SwfReader reader = new SwfReader(new MemoryStream(bytes));
            ReadSwf(reader);

            Log.WriteLine(Subsystem.LoadingGeneral,
                "swfVersion={0} scriptVersion={1} " +
                "frameRate={2} totalFrames={3} width={4} height={5} " +
                "bytesTotal={6} contentType={7} url={8}",
                this.contentLoaderInfo.swfVersion,
                this.contentLoaderInfo.scriptVersion,
                this.contentLoaderInfo.frameRate,
                this.contentLoaderInfo.totalFrames,
                this.contentLoaderInfo.width,
                this.contentLoaderInfo.height,
                this.contentLoaderInfo.bytesTotal,
                this.contentLoaderInfo.contentType,
                this.contentLoaderInfo.url);
        }

        /// <summary>
        /// Removes any loaded child of this loader.
        /// </summary>
        public void Unload()
        {
            this.m_jpegTables = null;
            this.content = null;
            this.contentLoaderInfo = null;
        }

        /// <summary>
        /// Not supported by the Loader class.
        /// </summary>
        public override void AddChild(DisplayObject child)
        {
            throw new global::System.NotSupportedException();
        }

        /// <summary>
        /// Not supported by the Loader class.
        /// </summary>
        public override void AddChildAt(DisplayObject child, int index)
        {
            throw new global::System.NotSupportedException();
        }

        /// <summary>
        /// Not supported by the Loader class.
        /// </summary>
        public override DisplayObject RemoveChild(DisplayObject child)
        {
            throw new global::System.NotSupportedException();
        }

        /// <summary>
        /// Not supported by the Loader class.
        /// </summary>
        public override DisplayObject RemoveChildAt(int index)
        {
            throw new global::System.NotSupportedException();
        }

        /// <summary>
        /// Not supported by the Loader class.
        /// </summary>
        public override void SetChildIndex(DisplayObject child, int index)
        {
            throw new global::System.NotSupportedException();
        }

        protected void ReadSwf(SwfReader reader)
        {
            this.presentedProcessPlaceObject2TagRatioWarning = false;

            ReadSwfHeader(reader);
            reader.swfVersion = this.contentLoaderInfo.swfVersion;

            MovieClip clip = ReadSwfTags(reader, true, this.contentLoaderInfo.totalFrames);

            this.content = clip;
            this.contentLoaderInfo.content = clip;
        }

        protected MovieClip ReadSwfTags(SwfReader reader, bool mainTimeline, int totalFrames)
        {
            MovieClip clip = new MovieClip(mainTimeline, totalFrames);

            // TODO bwmott 2013-06-30: Based on the AS3 documentation only the main timeline
            // clip should have its loaderInfo object set; however, if we do that then
            // it causes issues with MovieClip because they use loaderInfo to access the
            // symbol and character definitions.
            clip.loaderInfo = this.contentLoaderInfo;

            int currentFrame = 0;

            bool done = false;
            while (!done)
            {
                int tagCodeAndLength = reader.Ui16();
                int tag = (tagCodeAndLength >> 6);
                int length = (tagCodeAndLength & 0x3f);
                if (length == 0x3f)
                {
                    length = reader.Si32();
                }

                // Debug.Log("Tag: " + tag + " Length: " + length + " Frame: " + currentFrame);

                switch (tag)
                {
                    // End Tag
                    case 0:
                        done = true;
                        break;

                    // ShowFrame Tag
                    case 1:
                        currentFrame += 1;
                        break;

                    // DefineShape, DefineShape2, DefineShape3, & DefineShape4 Tags
                    case 2:
                    case 22:
                    case 32:
                    case 83:
                        ProcessDefineShapeTag(reader, tag);
                        break;

                    // DefineBits Tag
                    case 6:
                        ProcessDefineBitsTag(reader, length);
                        break;

                    // JPEGTables Tag
                    case 8:
                        ProcessJPEGTablesTag(reader, length);
                        break;

                    // SetBackground Tag
                    case 9:
                        ProcessSetBackgroundTag(reader);
                        break;

                    // DefineFont Tag
                    case 10:
                        throw new global::System.FormatException(
                            "DefineFont tag not supported! Use a newer version of the SWF file.");

                    // DefineFont2 & DefineFont3 Tags
                    case 48:
                    case 75:
                        ProcessDefineFontTag(reader, tag);
                        break;

                    // DefineText & DefineText2 Tags
                    case 11:
                    case 33:
                        ProcessDefineTextTag(reader, tag);
                        break;

                    // DoAction Tag
                    case 12:
                        ProcessDoActionTag(reader, clip, currentFrame);
                        break;

                    // DefineSound Tag
                    case 14:
                        ProcessDefineSound(reader, length, clip, currentFrame);
                        break;

                    // StartSound Tag
                    case 15:
                        ProcessStartSound(reader, clip, currentFrame);
                        break;

                    // SoundStreamHead and SoundStreamHead2 Tags
                    case 18:
                    case 45:
                        ProcessSoundStreamHead(reader, tag, length, clip, currentFrame);
                        break;

                    // SoundStreamBlock Tag
                    case 19:
                        ProcessSoundStreamBlock(reader, length, clip, currentFrame);
                        break;

                    // DefineBitsLossless & DefineBitsLossless2 Tags
                    case 20:
                    case 36:
                        ProcessDefineBitsLossless(reader, tag, length);
                        break;

                    // DefineBitsJPEG2 Tag
                    case 21:
                        ProcessDefineBitsJPEG2(reader, length);
                        break;

                    // PlaceObject2 Tag
                    case 26:
                        ProcessPlaceObject2Tag(reader, clip, currentFrame);
                        break;

                    // RemoveObject2 Tag
                    case 28:
                        ProcessRemoveObject2Tag(reader, clip, currentFrame);
                        break;

                    // DefineBitsJPEG3 Tag
                    case 35:
                        ProcessDefineBitsJPEG3(reader, length);
                        break;

                    // DefineEditText Tag
                    case 37:
                        ProcessDefineEditText(reader, length);
                        break;

                    // DefineSprite Tag
                    case 39:
                        ProcessDefineSpriteTag(reader, clip);
                        break;

                    // FrameLabel Tag
                    case 43:
                        ProcessFrameLabel(reader, length, clip, currentFrame);
                        break;

                    // ExportAssets Tag
                    case 56:
                        ProcessExportAssetsTag(reader);
                        break;

                    // FileAttributes Tag
                    case 69:
                        ProcessFileAttributesTag(reader);
                        break;

                    // DefineFontAlignZones Tag
                    case 73:
                        // Ignore font alignment zones
                        reader.Skip(length);
                        break;

                    // CSMTextSettings Tag
                    case 74:
                        // Ignore CSM text settings
                        reader.Skip(length);
                        break;

                    // SymbolClass Tag
                    case 76:
                        ProcessSymbolClassTag(reader);
                        break;

                    // Metadata Tag
                    case 77:
                        // Ignore metadata
                        reader.Skip(length);
                        break;

                    // DoABC Tag
                    case 82:
                        // Ignore ABC code
                        reader.Skip(length);
                        break;

                    // DefineSceneAndFrameLabelData Tag
                    case 86:
                        ProcessDefineSceneAndFrameLabelDataTag(reader, clip);
                        break;

                    // DefineFontName Tag
                    case 88:
                        // Skip the font name and copyright data
                        reader.Skip(length);
                        break;

                    // DefineFont4 Tag
                    case 91:
                        throw new global::System.FormatException(
                            "DefineFont4 tag not supported! Use Classic not TLF fonts.");

                    // Unhandled Tag
                    default:
                        // Let's skip the tags content and log an error
                        reader.Skip(length);
                        Log.Error(Subsystem.LoadingGeneral,
                            "Unhandled Tag: {0} (0x{0:X2}) Length: {1} Frame: {2}",
                            tag, tag, length, currentFrame);
                        break;
                }
            }

            clip.PostLoadInitialization();

            return clip;
        }

        protected void ReadSwfHeader(SwfReader reader)
        {
            uint header = reader.Ui32();
            if (((header & 0x00ffffff) != 0x00535746) && ((header & 0x00ffffff) != 0x00535743))
            {
                throw new global::System.FormatException("Not a valid SWF file format.");
            }

            this.contentLoaderInfo.swfVersion = (uint)((header >> 24) & 0xff);
            this.contentLoaderInfo.bytesTotal = (uint)reader.Ui32();

            // TODO bwmott 2013-06-21: Once asynch loading is supported this will need to be
            // changed so that it's updated as loading progresses
            this.contentLoaderInfo.bytesLoaded = this.contentLoaderInfo.bytesTotal;

            // Use zlib decompression stream if needed
            if ((header & 0xff) == 'C')
            {
                reader.UseZlibDecompression();
            }

            // Continue reading the SWF header
            Rectangle rect = reader.Rectangle();
            this.contentLoaderInfo.width = (int)rect.width;
            this.contentLoaderInfo.height = (int)rect.height;
            this.contentLoaderInfo.frameRate = reader.Ui16() / 256.0f;
            this.contentLoaderInfo.totalFrames = reader.Ui16();
            this.contentLoaderInfo.contentType = "application/x-shockwave-flash";
        }

        protected void ProcessSetBackgroundTag(SwfReader reader)
        {
            this.contentLoaderInfo.color = reader.Color(false);
        }

        protected void ProcessDefineShapeTag(SwfReader reader, int tag)
        {
            Sprite shape = new Sprite();
            shape.loaderInfo = this.contentLoaderInfo;

            bool shape4 = (tag == 83);

            int id = reader.Ui16();

            // Read bounds of shape. We don't store them for now since we'll
            // dynamically calculate them for scripting.
            reader.Rectangle();

            if (shape4)
            {
                // Read edge bounds of shape. We don't store them for now since we'll
                // dynamically calculate them for scripting.
                reader.Rectangle();

                // Read Shape4 flags but we do not use them (they do not appear to be necessary)
                reader.Ui8();

                //bool usesFillWindingRule = flags & 0x04;
                //bool usesNonScalingStrokes = flags & 0x02;
                //bool usesScalingStrokes = flags & 0x01;
            }

            List<object> fillStyles = new List<object>();
            List<object> lineStyles = new List<object>();
            int numFillBits = 0;
            int numLineBits = 0;
            float scale = 1 / 20.0f;

            // Read in the fill and line styles
            ProcessStyles(reader, tag, fillStyles, lineStyles, ref numFillBits, ref numLineBits);
            shape.graphics = SortShapeRecord(shape.graphics, fillStyles, lineStyles, reader,
                numFillBits, numLineBits, tag, scale);

            // Add the defined shape to the loader context's dictionary
            context.dictionary.Add(id, shape);
        }

        protected void ProcessStyles(SwfReader reader, int tag, List<object> fillStyles,
            List<object> lineStyles, ref int numFillBits, ref int numLineBits)
        {
            bool shape1 = (tag == 2);
            bool shape2 = (tag == 22);
            bool shape3 = (tag == 32);
            bool shape4 = (tag == 83);

            // --- Read Fill Styles ---
            int fillStyleCount = reader.Ui8();
            if ((shape2 || shape3 || shape4) && (fillStyleCount == 0xff))
            {
                fillStyleCount = reader.Ui16();
            }

            for (int i = 0; i < fillStyleCount; ++i)
            {
                object fillStyle = ProcessFillStyle(reader, tag);
                fillStyles.Add(fillStyle);
            }

            // --- Read Line Styles ---
            int lineStyleCount = reader.Ui8();
            if (lineStyleCount == 0xff)
            {
                lineStyleCount = reader.Ui16();
            }

            if (!shape4)
            {
                // Read LINESTYLE
                for (int i = 0; i < lineStyleCount; ++i)
                {
                    // Read width of line in twips
                    float w = reader.Ui16() / 20.0f;

                    // Read color of line
                    uint c = reader.Color(!(shape1 || shape2));

                    // Add line to lineStyles
                    float r;
                    if (w <= 3.0f)
                    {
                        r = w / 2.0f;
                    }
                    else
                    {
                        r = 2.0f;
                    }

                    lineStyles.Add(new LineStyle(c, w, r));
                }
            }
            else
            {
                // Read LINESTYLE2
                for (int i = 0; i < lineStyleCount; ++i)
                {
                    // Read width of line in twips
                    float w = reader.Ui16() / 20.0f;

                    int flags = reader.Ui8();
                    //int startCapStyle = (flags >> 6) & 0x03;
                    int joinStyle = (flags >> 4) & 0x03;
                    bool hasFillFlag = ((flags & 0x08) != 0);
                    //bool noHScaleFlag = (flags & 0x04);
                    //bool noVScaleFlag = (flags & 0x02);
                    //bool pixelHintingFlag = (flags & 0x01);

                    flags = reader.Ui8();
                    //bool noClose = (flags & 0x04);
                    //int endCapStyle = (flags & 0x03);

                    if (joinStyle == 2)
                    {
                        // Read the MiterLimitFactor
                        reader.Fixed8();
                    }

                    if (!hasFillFlag)
                    {
                        uint c = reader.Color(true);
                        float r;
                        if (w <= 2.0f)
                        {
                            r = w / 2.0f;
                        }
                        else
                        {
                            r = 1.0f;
                        }
                        lineStyles.Add(new LineStyle(c, w, r));
                    }
                    else
                    {
                        ProcessFillStyle(reader, tag);
                    }
                }
            }

            reader.Align();
            numFillBits = (int)reader.Ui32(4);
            numLineBits = (int)reader.Ui32(4);
        }

        protected object ProcessFillStyle(SwfReader reader, int tag)
        {
            bool shape3 = (tag == 32);
            bool shape4 = (tag == 83);
            int fillStyleType = reader.Ui8();

            // Solid Fill
            if (fillStyleType == 0x00)
            {
                uint argb = reader.Color(shape3 || shape4);
                GraphicsSolidFill fill = new GraphicsSolidFill(argb & 0x00ffffff,
                    (argb >> 24) / 255.0f);
                return fill;
            }
            // Gradient Fill
            else if ((fillStyleType == 0x10) || (fillStyleType == 0x12)
                || (fillStyleType == 0x13))
            {
                Matrix gradientMatrix = reader.Matrix();
                float tx = gradientMatrix.tx;
                float ty = gradientMatrix.ty;
                gradientMatrix.tx = gradientMatrix.ty = 0.0f;
                gradientMatrix.Concat(new Matrix(1.0f / 20.0f, 0.0f, 0.0f,
                    1.0f / 20.0f, 0.0f, 0.0f));
                gradientMatrix.tx = tx;
                gradientMatrix.ty = ty;

                int bits = reader.Ui8();
                int spreadModeIndex = (bits >> 6) & 0x03;
                int interpolationModeIndex = (bits >> 4) & 0x03;
                int numGradients = bits & 0x0f;

                string spreadMode = "reserved";
                if (spreadModeIndex == 0)
                    spreadMode = SpreadMethod.PAD;
                else if (spreadModeIndex == 1)
                    spreadMode = SpreadMethod.REFLECT;
                else if (spreadModeIndex == 2)
                    spreadMode = SpreadMethod.REPEAT;
                else
                    Log.Warning(Subsystem.LoadingGeneral,
                        "Illegal SpreadMethod defined for gradient fill.");

                string interpolationMode = "reserved";
                if (interpolationModeIndex == 0)
                    interpolationMode = InterpolationMethod.RGB;
                else if (interpolationModeIndex == 1)
                    interpolationMode = InterpolationMethod.LINEAR_RGB;
                else
                    Log.Warning(Subsystem.LoadingGeneral,
                        "Illegal InterpolationMethod defined for gradient fill.");

                uint[] colors = new uint[numGradients];
                float[] alphas = new float[numGradients];
                byte[] ratios = new byte[numGradients];

                for (int j = 0; j < numGradients; ++j)
                {
                    byte ratio = (byte)reader.Ui8();
                    uint argb = reader.Color(shape3 || shape4);

                    colors[j] = argb & 0x00ffffff;
                    alphas[j] = ((argb >> 24) / 255.0f);
                    ratios[j] = ratio;
                }

                float focalPoint = 0.0f;
                string type;
                if (fillStyleType == 0x10)
                {
                    type = GradientType.LINEAR;
                }
                else if (fillStyleType == 0x12)
                {
                    type = GradientType.RADIAL;
                }
                else
                {
                    type = GradientType.RADIAL;
                    focalPoint = reader.Fixed8();
                }

                GraphicsGradientFill fill = new GraphicsGradientFill(type, colors, alphas,
                    ratios, gradientMatrix, spreadMode, interpolationMode, focalPoint);
                return fill;
            }
            // Bitmap Fill
            else if ((fillStyleType >= 0x40) && (fillStyleType <= 0x43))
            {
                int bitmapId = reader.Ui16();
                Matrix bitmapMatrix = reader.Matrix();
                bitmapMatrix.Invert();
                bitmapMatrix.Concat(new Matrix(20.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.0f));

                BitmapData bitmapData = null;
                if (context.dictionary.ContainsKey(bitmapId))
                {
                    bitmapData = (BitmapData)context.dictionary[bitmapId];
                }

                GraphicsBitmapFill fill = new GraphicsBitmapFill(bitmapData,
                    bitmapMatrix, (fillStyleType == 0x40) || (fillStyleType == 0x42),
                    (fillStyleType == 0x40) || (fillStyleType == 0x41));

                return fill;
            }
            else
            {
                Log.Error(Subsystem.LoadingGeneral, "Unsupported fill type!");
                return null;
            }
        }

        protected void ProcessJPEGTablesTag(SwfReader reader, int length)
        {
            if (length > 0)
            {
                m_jpegTables = reader.ByteArray((uint)length);
            }
        }

        protected void ProcessDefineBitsTag(SwfReader reader, int length)
        {
            int id = reader.Ui16();
            byte[] imageData = reader.ByteArray((uint)(length - 2));

            // TODO bwmott 2013-01-03: Need to combine any JPEGTables data
            if (m_jpegTables != null)
            {
                Log.Error(Subsystem.LoadingGeneral, "JPEGTables use not supported!");
            }

            Texture2D texture = new Texture2D(4, 4, TextureFormat.DXT5, false);
            texture.LoadImage(imageData);
            texture.filterMode = FilterMode.Bilinear;
            texture.wrapMode = TextureWrapMode.Repeat;
            BitmapData bitmapData = new BitmapData(texture);

            // Add the defined bitmap to the loader context's dictionary
            context.dictionary.Add(id, bitmapData);
        }

        protected void ProcessDefineBitsJPEG2(SwfReader reader, int length)
        {
            int id = reader.Ui16();
            byte[] imageData = reader.ByteArray((uint)(length - 2));

            if (!((imageData[0] == 0xff) && (imageData[1] == 0xd8)) &&
                !((imageData[0] == 0x89) && (imageData[1] == 0x50) && (imageData[2] == 0x4e)))
            {
                Log.Error(Subsystem.LoadingGeneral, "DefineBitsJPEG2 unsupported bitmap format!");
            }

            Texture2D texture = new Texture2D(4, 4, TextureFormat.DXT5, false);
            texture.LoadImage(imageData);
            texture.filterMode = FilterMode.Bilinear;
            texture.wrapMode = TextureWrapMode.Repeat;
            BitmapData bitmapData = new BitmapData(texture);

            // Add the defined bitmap to the loader context's dictionary
            context.dictionary.Add(id, bitmapData);
        }

        protected void ProcessDefineBitsJPEG3(SwfReader reader, int length)
        {
            int id = reader.Ui16();
            uint alphaDataOffset = reader.Ui32();
            byte[] imageData = reader.ByteArray(alphaDataOffset);
            uint alphaLength = (uint)length - 2 - 4 - alphaDataOffset;
            byte[] alphaData = reader.ByteArray(alphaLength);

            if (!((imageData[0] == 0xff) && (imageData[1] == 0xd8)) &&
                !((imageData[0] == 0x89) && (imageData[1] == 0x50) && (imageData[2] == 0x4e)))
            {
                Log.Error(Subsystem.LoadingGeneral, "DefineBitsJPEG3 unsupported bitmap format!");
            }

            Texture2D texture = new Texture2D(4, 4, TextureFormat.ARGB32, false);
            texture.LoadImage(imageData);

            // See if there's an alpha data array that needs to be combined with the image data
            if (alphaData != null)
            {
                byte[] uncompressedAlphaData = new byte[texture.width * texture.height];
                using (var input = new ZlibStream(new MemoryStream(alphaData),
                    CompressionMode.Decompress))
                {
                    input.Read(uncompressedAlphaData, 0, texture.width * texture.height);
                }

                Texture2D atex = new Texture2D(texture.width, texture.height,
                    TextureFormat.ARGB32, false);

                Color32[] colors = texture.GetPixels32();
                for (int y = 0; y < texture.height; ++y)
                {
                    for (int x = 0; x < texture.width; ++x)
                    {
                        colors[(y * texture.width) + x].a =
                            uncompressedAlphaData[((texture.height - y - 1) * texture.width) + x];
                    }
                }

                atex.SetPixels32(colors);
                atex.Apply();
                texture = atex;
            }

            texture.filterMode = FilterMode.Bilinear;
            texture.wrapMode = TextureWrapMode.Repeat;
            BitmapData bitmapData = new BitmapData(texture);

            // Add the defined bitmap to the loader context's dictionary
            context.dictionary.Add(id, bitmapData);
        }

        protected void ProcessDefineBitsLossless(SwfReader reader, int tag, int length)
        {
            int id = reader.Ui16();
            int bitmapFormat = reader.Ui8();
            int bitmapWidth = reader.Ui16();
            int bitmapHeight = reader.Ui16();

            uint[] colorMap = null;
            byte[] imageData = null;

            if (bitmapFormat < 3 || bitmapFormat > 5)
            {
                Log.Error(Subsystem.LoadingGeneral, "DefineBitsLossless unsupported format!");
            }

            // DefineBitsLossless2 provides alpha channel data
            bool alpha = (tag == 36);

            // Compute pitch for each row of image data
            int pitch = bitmapWidth * ((bitmapFormat == 3) ? 1 : ((bitmapFormat == 4) ? 2 : 4));
            pitch = (pitch + 3) & ~0x03;

            // Read 8-bit colormapped image data
            if (bitmapFormat == 3)
            {
                int tableSize = reader.Ui8() + 1;
                colorMap = new uint[tableSize];

                // Read ZLIB compressed image data
                byte[] data = reader.ByteArray((uint)(length - 8));
                MemoryStream stream = new MemoryStream(data);
                using (var input = new ZlibStream(stream, CompressionMode.Decompress))
                {
                    byte[] buf = new byte[4];

                    for (int i = 0; i < tableSize; ++i)
                    {
                        if (alpha)
                        {
                            input.Read(buf, 0, 4);
                            colorMap[i] = ((uint)buf[2]) | ((uint)buf[1] << 8) |
                                ((uint)buf[0] << 16) | ((uint)buf[3] << 24);
                        }
                        else
                        {
                            input.Read(buf, 0, 3);
                            colorMap[i] = ((uint)buf[2]) | ((uint)buf[1] << 8) |
                                ((uint)buf[0] << 16) | 0xFF000000;
                        }
                    }

                    imageData = new byte[bitmapHeight * pitch];
                    input.Read(imageData, 0, imageData.Length);
                }
            }
            // Read 15-bit, 24-bit, or 32-bit image data
            else
            {
                // Read ZLIB compressed image data
                byte[] data = reader.ByteArray((uint)(length - 7));
                MemoryStream stream = new MemoryStream(data);
                using (var input = new ZlibStream(stream, CompressionMode.Decompress))
                {
                    imageData = new byte[bitmapHeight * pitch];
                    input.Read(imageData, 0, imageData.Length);
                }
            }

            Texture2D texture = new Texture2D(bitmapWidth, bitmapHeight,
                    alpha ? TextureFormat.ARGB32 : TextureFormat.RGB24, false);

            // Create texture from image data
            if (bitmapFormat == 3)
            {
                Color32[] colors = new Color32[bitmapWidth * bitmapHeight];
                for (int y = 0; y < bitmapHeight; ++y)
                {
                    int destOffset = (bitmapHeight - 1 - y) * bitmapWidth;
                    for (int x = 0; x < bitmapWidth; ++x)
                    {
                        uint color = colorMap[imageData[y * pitch + x]];

                        colors[destOffset + x].a = (byte)(color >> 24);
                        colors[destOffset + x].r = (byte)((color >> 16) & 0xff);
                        colors[destOffset + x].g = (byte)((color >> 8) & 0xff);
                        colors[destOffset + x].b = (byte)((color) & 0xff);
                    }
                }
                texture.SetPixels32(colors);
                texture.Apply();
            }
            else if (bitmapFormat == 4)
            {
                Color32[] colors = new Color32[bitmapWidth * bitmapHeight];
                for (int y = 0; y < bitmapHeight; ++y)
                {
                    int destOffset = (bitmapHeight - 1 - y) * bitmapWidth;
                    for (int x = 0; x < bitmapWidth; ++x)
                    {
                        uint color = ((uint)imageData[y * pitch + x * 2] << 8)
                            | ((uint)imageData[y * pitch + x * 2 + 1]);

                        colors[destOffset + x].r = (byte)((((color >> 10) & 0x1f) << 3) | 0x07);
                        colors[destOffset + x].g = (byte)((((color >> 5) & 0x1f) << 3) | 0x07);
                        colors[destOffset + x].b = (byte)((((color) & 0x1f) << 3) | 0x07);
                    }
                }
                texture.SetPixels32(colors);
                texture.Apply();
            }
            else if (bitmapFormat == 5)
            {
                if (alpha)
                {
                    Color32[] colors = new Color32[bitmapWidth * bitmapHeight];
                    for (int y = 0; y < bitmapHeight; ++y)
                    {
                        int destOffset =(bitmapHeight - 1 - y) * bitmapWidth;
                        for (int x = 0; x < bitmapWidth; ++x)
                        {
                            colors[destOffset + x].a = imageData[y * pitch + x * 4];
                            colors[destOffset + x].r = imageData[y * pitch + x * 4 + 1];
                            colors[destOffset + x].g = imageData[y * pitch + x * 4 + 2];
                            colors[destOffset + x].b = imageData[y * pitch + x * 4 + 3];
                        }
                    }
                    texture.SetPixels32(colors);                
                }
                else
                {
                    Color32[] colors = new Color32[bitmapWidth * bitmapHeight];
                    for (int y = 0; y < bitmapHeight; ++y)
                    {
                        int destOffset =(bitmapHeight - 1 - y) * bitmapWidth;
                        for (int x = 0; x < bitmapWidth; ++x)
                        {
                            colors[destOffset + x].r = imageData[y * pitch + x * 4 + 1];
                            colors[destOffset + x].g = imageData[y * pitch + x * 4 + 2];
                            colors[destOffset + x].b = imageData[y * pitch + x * 4 + 3];
                        }
                    }
                    texture.SetPixels32(colors);
                }
                texture.Apply();
            }

            texture.filterMode = FilterMode.Bilinear;
            texture.wrapMode = TextureWrapMode.Repeat;
            BitmapData bitmapData = new BitmapData(texture);

            // Add the defined bitmap to the loader context's dictionary
            context.dictionary.Add(id, bitmapData);
        }

        protected Shape ProcessGlyphShape(SwfReader reader, float scale)
        {
            Shape shape = new Shape();
            shape.loaderInfo = this.contentLoaderInfo;

            reader.Align();
            int numFillBits = (int)reader.Ui32(4);
            reader.Ui32(4); // numLineBits isn't used
            List<object> fillStyles = new List<object>();
            List<object> lineStyles = new List<object>();
            fillStyles.Add (new GraphicsSolidFill(0x000000));

            // numLineBits and tag will not be used, pass as 0
            shape.graphics = SortShapeRecord(shape.graphics, fillStyles, lineStyles, reader,
                numFillBits, 0, 0, scale);

            return shape;
        }

        private class PointComparer : IComparer<Vector2>
        {
            public int Compare(Vector2 first, Vector2 second)
            {
                if (first.x == second.x)
                    return (int)(first.y - second.y);
                else
                    return (int)(first.x - second.x);
            }
        }

        // Duplicate code used in ProcessDefineShapeTag() and ProcessGlyphShape()
        private Graphics SortShapeRecord(Graphics shape, List<object> fillStyles,
            List<object> lineStyles, SwfReader reader, int numFillBits, int numLineBits,
            int tag, float scale)
        {
            Dictionary<object, List<Edge>> fillStyleEdgeMap = new Dictionary<object, List<Edge>>();

            // --- SHAPE RECORD reading ---

            object fillStyle0 = null;
            object fillStyle1 = null;
            object lineStyle = null;

            int currentX = 0;
            int currentY = 0;

            for (;;)
            {
                int typeFlag = (int)reader.Ui32(1);

                // Is this a non-edge record
                if (typeFlag == 0)
                {
                    int flagBits = (int)reader.Ui32(5);

                    // EndShapeRecord?
                    if (flagBits == 0x00)
                    {
                        break;
                    }

                    // MoveTo?
                    if ((flagBits & 0x01) != 0)
                    {
                        int nbits = (int)reader.Ui32(5);

                    currentX = reader.Si32(nbits);
                    currentY = reader.Si32(nbits);
                    }

                    // FillStyle0 Change?
                    if (((flagBits & 0x02) != 0) && (numFillBits > 0))
                    {
                        int fillStyle = (int)reader.Ui32(numFillBits);
                        fillStyle0 = (fillStyle == 0) ? null : fillStyles[fillStyle - 1];

                        if ((fillStyle0 != null) && (!fillStyleEdgeMap.ContainsKey(fillStyle0)))
                        {
                            fillStyleEdgeMap[fillStyle0] = new List<Edge>();
                        }
                    }

                    // FillStyle1 Change?
                    if (((flagBits & 0x04) != 0) && (numFillBits > 0))
                    {
                        int fillStyle = (int)reader.Ui32(numFillBits);
                        fillStyle1 = (fillStyle == 0) ? null : fillStyles[fillStyle - 1];

                        if ((fillStyle1 != null) && (!fillStyleEdgeMap.ContainsKey(fillStyle1)))
                        {
                            fillStyleEdgeMap[fillStyle1] = new List<Edge>();
                        }
                    }

                    // LineStyle Change?
                    if (((flagBits & 0x08) != 0) && (numLineBits > 0))
                    {
                        int style = (int)reader.Ui32(numLineBits);
                        lineStyle = (style == 0) ? null : lineStyles[style - 1];

                        if ((lineStyle != null) && (!fillStyleEdgeMap.ContainsKey(lineStyle)))
                        {
                            // Go ahead and add it to the fillstyle edge map for now
                            fillStyleEdgeMap[lineStyle] = new List<Edge>();
                        }
                    }

                    // List of new fill and line styles
                    if ((flagBits & 0x10) != 0)
                    {
                        fillStyles.Clear();
                        ProcessStyles(reader, tag, fillStyles, lineStyles,
                            ref numFillBits, ref numLineBits);
                    }
                }
                else
                {
                    int straightFlag = (int)reader.Ui32(1);
                    if (straightFlag == 0)
                    {
                        // Curved edge
                        int nbits = 2 + (int)reader.Ui32(4);

                        int controlDeltaX = reader.Si32(nbits);
                        int controlDeltaY = reader.Si32(nbits);
                        int anchorDeltaX = reader.Si32(nbits);
                        int anchorDeltaY = reader.Si32(nbits);

                        Edge e = new Edge(currentX, currentY,
                            currentX + controlDeltaX, currentY + controlDeltaY,
                            currentX + controlDeltaX + anchorDeltaX,
                            currentY + controlDeltaY + anchorDeltaY);
                        if (fillStyle0 != null)
                        {
                            fillStyleEdgeMap[fillStyle0].Add(e);
                        }
                        if (fillStyle1 != null)
                        {
                            fillStyleEdgeMap[fillStyle1].Add(e.Reverse());
                        }
                        if (lineStyle != null)
                        {
                            fillStyleEdgeMap[lineStyle].Add (e);
                        }

                        currentX = e.x2;
                        currentY = e.y2;
                    }
                    else
                    {
                        // Straight edge
                        int nbits = 2 + (int)reader.Ui32(4);
                        int generalLineFlag = (int)reader.Ui32(1);
                        int dx = 0;
                        int dy = 0;

                        if (generalLineFlag == 0)
                        {
                            int vertLineFlag = (int)reader.Ui32(1);
                            if (vertLineFlag == 0)
                            {
                                dx = reader.Si32(nbits);
                            }
                            else
                            {
                                dy = reader.Si32(nbits);
                            }
                        }
                        else
                        {
                            // General line
                            dx = reader.Si32(nbits);
                            dy = reader.Si32(nbits);
                        }
                        Edge e = new Edge(currentX, currentY, currentX + dx, currentY + dy);
                        if (fillStyle0 != null)
                        {
                            fillStyleEdgeMap[fillStyle0].Add(e);
                        }
                        if (fillStyle1 != null)
                        {
                            fillStyleEdgeMap[fillStyle1].Add(e.Reverse());
                        }
                        if (lineStyle != null)
                        {
                            fillStyleEdgeMap[lineStyle].Add(e);
                        }

                        currentX = e.x2;
                        currentY = e.y2;
                    }
                }
            }

            // All of the edges have been loaded from the SWF file for this shape, now we need to
            // go through the edges associated with each fill style to find all of the closed paths.
            foreach (var entry in fillStyleEdgeMap)
            {
                if (entry.Key is LineStyle)
                {
                    shape.LineStyle ((LineStyle)entry.Key);
                }
                else
                {
                    shape.BeginFill(entry.Key);
                }

                List<List<Edge>> sorted = new List<List<Edge>>();
                SortedList<Vector2, List<Edge>> edges = new
                    SortedList<Vector2, List<Edge>>(new PointComparer());

                // Load the edges into the sorted list
                foreach (Edge e in entry.Value)
                {
                    List<Edge> val;
                    Vector2 v = new Vector2(e.x1, e.y1);
                    if (edges.TryGetValue(v, out val))
                    {
                        val.Add(e);
                    }
                    else
                    {
                        val = new List<Edge>();
                        val.Add(e);
                        edges.Add(v, val);
                    }
                }

                List<Edge> currentPath;

                while (edges.Count > 0)
                {
                    currentPath = new List<Edge>();
                    Edge edge = edges[edges.Keys[0]][0];
                    Vector2 p1 = new Vector2(edge.x1, edge.y1);

                    if (edges[p1].Count > 1)
                        edges[p1].RemoveAt(0);
                    else edges.Remove(p1);

                    currentPath.Add(edge);

                    Vector2 p2;

                    while (edges.Count > 0)
                    {
                        // Add the connecting edge
                        p2 = new Vector2(edge.x2, edge.y2);

                        if (edges.ContainsKey(p2))
                        {
                            edge = edges[p2][0];
                            if (edges[p2].Count > 1)
                                edges[p2].RemoveAt(0);
                            else
                                edges.Remove(p2);

                            currentPath.Add(edge);

                            // Are we closing the path?
                            if ((edge.x2 == currentPath[0].x1) && (edge.y2 == currentPath[0].y1))
                            {
                                // Yes, it is a closed path so let's add it to sorted
                                sorted.Add(currentPath);

                                // Now get ready to process the next path, if any exist
                                currentPath = new List<Edge>();

                                if (edges.Count > 0)
                                {
                                    edge = edges[edges.Keys[0]][0];
                                    p1 = new Vector2(edge.x1, edge.y1);
                                    if (edges[p1].Count > 1)
                                        edges[p1].RemoveAt(0);
                                    else edges.Remove(p1);

                                    currentPath.Add(edge);
                                }
                            }
                        }
                        // Otherwise, the path is unconnected
                        else
                        {
                            // We are done with this path,so add it to sorted
                            sorted.Add(currentPath);

                            // Now get ready to process the next path
                            currentPath = new List<Edge>();

                            edge = edges[edges.Keys[0]][0];
                            p1 = new Vector2(edge.x1, edge.y1);
                            if (edges[p1].Count > 1)
                                edges[p1].RemoveAt(0);
                            else edges.Remove(p1);

                            currentPath.Add(edge);
                            continue;
                        }
                    }
                }

                foreach (List<Edge> path in sorted)
                {
                    shape.MoveTo(path[0].x1 * scale, path[0].y1 * scale);
                    foreach (Edge e in path)
                    {
                        if (e.type == Edge.Type.Line)
                        {
                            shape.LineTo(e.x2 * scale, e.y2 * scale);
                        }
                        else
                        {
                            shape.CurveTo(e.cx * scale, e.cy * scale, e.x2 * scale, e.y2 * scale);
                        }
                    }
                }

                shape.EndFill();
            }

            return shape;
        }

        protected void ProcessDefineFontTag(SwfReader reader, int tag)
        {
            int id = reader.Ui16();
            int fontFlags = reader.Ui8();

            bool hasLayout = (fontFlags & 0x80) != 0x00;
            //bool shiftJis = (fontFlags & 0x40) != 0x00;
            //bool smallText = (fontFlags & 0x20) != 0x00;
            //bool ansi = (fontFlags & 0x10) != 0x00;
            bool wideOffsets = (fontFlags & 0x08) != 0x00;
            bool wideCodes = (fontFlags & 0x04) != 0x00;
            bool italic = (fontFlags & 0x02) != 0x00;
            bool bold = (fontFlags & 0x01) != 0x00;

            string fontStyle = Flare.Text.FontStyle.GetStyle(bold, italic);
            int languageCode = reader.Ui8();

            int fontNameLen = reader.Ui8();
            string fontName = reader.String(fontNameLen);

            EmbeddedFont font = new EmbeddedFont(fontName, fontStyle);
            font.languageCode = languageCode;

            int numGlyphs = reader.Ui16();

            List<uint> offsetTable = new List<uint>();
            if (wideOffsets)
            {
                for (int i = 0; i < numGlyphs; ++i)
                {
                    offsetTable.Add(reader.Ui32());
                }
            }
            else
            {
                for (int i = 0; i < numGlyphs; ++i)
                {
                    offsetTable.Add((uint)reader.Ui16());
                }
            }

            // Read codeTableOffset (not currently used)
            if (numGlyphs > 0)
            {
                if (wideOffsets)
                    reader.Ui32();
                else
                    reader.Ui16();
            }

            // TODO bwmott 2013-05-11: The code to read glyph shapes does not follow the
            // SWF File format which indicates the offset table should be used to "seek"
            // to the correct position to read shape records. Since compressed streams
            // do not support seeks, we would need to read the data into a memory buffer
            // and use it for accessing the shape records. Luckily, Flash seems to
            // create SWF files which have the shape records in ascending order without
            // any padding so this doesn't appear to be necessary for now.

            // Read glyph shapes
            List<Shape> glyphShapes = new List<Shape>();
            for (int i = 0; i < numGlyphs; ++i)
            {
                glyphShapes.Add(ProcessGlyphShape(reader, 1.0f / ((tag == 75) ? 20.0f : 1.0f)));
            }

            font.glyphShapes = glyphShapes.ToArray();

            // Read code table for the font
            Dictionary<uint, char> codeTable = new Dictionary<uint, char>();
            if (wideCodes)
            {
                for (uint i = 0; i < numGlyphs; ++i)
                {
                    char c = (char)reader.Ui16();
                    codeTable[i] = c;
                }
            }
            else
            {
                for (uint i = 0; i < numGlyphs; ++i)
                {
                    char c = (char)reader.Ui8();
                    codeTable[i] = c;
                }
            }
            font.codeTable = codeTable;

            if (hasLayout)
            {
                font.hasLayout = true;

                font.ascent = reader.Ui16() / 20.0f / 1024.0f;
                font.descent = reader.Ui16() / 20.0f / 1024.0f;
                font.leading = reader.Si16() / 20.0f / 1024.0f;

                float[] fontAdvanceTable = new float[numGlyphs];
                for (int i = 0; i < numGlyphs; ++i)
                {
                    fontAdvanceTable[i] = reader.Si16() / 20.0f;
                }
                font.advanceTable = fontAdvanceTable;

                Rectangle[] fontBoundsTable = new Rectangle[numGlyphs];
                for (int i = 0; i < numGlyphs; ++i)
                {
                    fontBoundsTable[i] = reader.Rectangle();
                }
                font.boundsTable = fontBoundsTable;

                int kerningCount = reader.Ui16();
                EmbeddedFont.KerningRecord[] kerningRecords =
                    new EmbeddedFont.KerningRecord[kerningCount];
                for (int i = 0; i < kerningCount; ++i)
                {
                    char code1 = (char)(wideCodes ? reader.Ui16() : reader.Ui8());
                    char code2 = (char)(wideCodes ? reader.Ui16() : reader.Ui8());
                    float adjust = reader.Si16() / 20.0f;

                    kerningRecords[i] = new EmbeddedFont.KerningRecord(code1, code2, adjust);
                }
                font.kerningRecords = kerningRecords;
            }

            ApplicationDomain.currentDomain.AddEmbeddedFont(font.fullFontName, font);

            context.dictionary.Add(id, font);
        }

        protected void ProcessDefineTextTag(SwfReader reader, int tag)
        {
            int id = reader.Ui16();
            Rectangle textBounds = reader.Rectangle();
            Matrix textMatrix = reader.Matrix();

            int glyphBits = reader.Ui8();
            int advanceBits = reader.Ui8();

            int currentFontId = 0;
            uint currentColor = 0;
            float currentHeight = 0.0f;
            float xOffset = 0.0f;
            float yOffset = 0.0f;

            List<StaticText.TextRecord> textRecords = new List<StaticText.TextRecord>();

            for (;;)
            {
                int textRecordHeader = reader.Ui8();

                // See if we've reached the end of the text records
                if (textRecordHeader == 0x00)
                {
                    break;
                }

                bool hasFont = (textRecordHeader & 0x08) != 0x00;
                bool hasColor = (textRecordHeader & 0x04) != 0x00;
                bool hasYOffset = (textRecordHeader & 0x02) != 0x00;
                bool hasXOffset = (textRecordHeader & 0x01) != 0x00;

                if (hasFont)
                {
                    currentFontId = reader.Ui16();
                }
                if (hasColor)
                {
                    currentColor = (tag == 11) ? reader.Color(false) : reader.Color(true);
                }
                if (hasXOffset)
                {
                    xOffset =  reader.Si16() / 20.0f;
                }
                if (hasYOffset)
                {
                    yOffset = reader.Si16() / 20.0f;
                }
                if (hasFont)
                {
                    currentHeight = reader.Ui16() / 20.0f;
                }

                float entryAdvance = 0.0f;
                int glyphCount = reader.Ui8();
                StaticText.GlyphEntry[] entries = new StaticText.GlyphEntry[glyphCount];
                reader.Align();
                for (int i = 0; i < glyphCount; ++i)
                {
                    uint glyphIndex = reader.Ui32(glyphBits);
                    float glyphAdvance = reader.Si32(advanceBits) / 20.0f;
                    entries[i] = new StaticText.GlyphEntry(glyphIndex, glyphAdvance);
                    entryAdvance += glyphAdvance;
                }

                var font = (Flare.Text.EmbeddedFont)context.dictionary[currentFontId];

                StaticText.TextRecord textRecord = new StaticText.TextRecord(font,
                    currentColor, xOffset, yOffset, currentHeight, entries);

                xOffset += entryAdvance;

                textRecords.Add(textRecord);
            }

            StaticText text = new StaticText(textBounds, textMatrix, textRecords.ToArray());
            text.loaderInfo = this.contentLoaderInfo;

            // Add the defined shape to the loader context's dictionary
            context.dictionary.Add(id, text);
        }

        protected void ProcessDoActionTag(SwfReader reader, MovieClip clip, int currentFrame)
        {
            AS2 as2 = new AS2();

            bool done = false;
            while(!done)
            {
                int code = reader.Ui8();

                int length = 0;
                if (code >= 0x80)
                {
                    length = reader.Ui16();
                }

                switch (code)
                {
                    // End
                    case 0x00:
                    {
                        done = true;
                        break;
                    }

                    // ActionGotoFrame
                    case 0x81:
                    {
                        int frame = reader.Ui16();
                        as2.AddAction(new AS2.GotoFrameAction(frame));
                        break;
                    }

                    // ActionGetURL
                    case 0x83:
                    {
                        string url = reader.String();
                        string target = reader.String();
                        as2.AddAction(new AS2.GetURLAction(url, target));
                        break;
                    }

                    // ActionNextFrame
                    case 0x04:
                    {
                        as2.AddAction(new AS2.NextFrameAction());
                        break;
                    }

                    // ActionPreviousFrame
                    case 0x05:
                    {
                        as2.AddAction(new AS2.PreviousFrameAction());
                        break;
                    }

                    // ActionPlay
                    case 0x06:
                    {
                        as2.AddAction(new AS2.PlayAction());
                        break;
                    }

                    // ActionStop
                    case 0x07:
                    {
                        as2.AddAction(new AS2.StopAction());
                        break;
                    }

                    // ActionSetTarget
                    case 0x8B:
                    {
                        string target = reader.String();
                        as2.AddAction(new AS2.SetTargetAction(target));
                        break;
                    }

                    // ActionSetTarget
                    case 0x8C:
                    {
                        string label = reader.String();
                        as2.AddAction(new AS2.GotoLabelAction(label));
                        break;
                    }

                    default:
                    {
                        // Unknown (or unhandled) script action so skip data and log error
                        reader.Skip(length);
                        Log.Error(Subsystem.LoadingGeneral,
                            "Unhandled AS2 Action: {0} (0x{0:X2}) Length: {1}", code, code, length);
                        break;
                    }
                }
            }

            // Add AS2 script to the timeline
            clip.timeline.AddActionAt(new MovieClip.AS2Action(as2), currentFrame);
        }

        private bool presentedProcessPlaceObject2TagRatioWarning = false;
        private bool presentedProcessPlaceObject2ClipActionWarning = false;

        protected void ProcessPlaceObject2Tag(SwfReader reader, MovieClip clip, int currentFrame)
        {
            reader.Align();
            bool placeFlagHasClipActions = ((int)reader.Ui32(1) != 0);
            bool placeFlagHasClipDepth = ((int)reader.Ui32(1) != 0);
            bool placeFlagHasName = ((int)reader.Ui32(1) != 0);
            bool placeFlagHasRatio = ((int)reader.Ui32(1) != 0);
            bool placeFlagHasColorTransform = ((int)reader.Ui32(1) != 0);
            bool placeFlagHasMatrix = ((int)reader.Ui32(1) != 0);
            bool placeFlagHasCharacter = ((int)reader.Ui32(1) != 0);
            bool placeFlagMove = ((int)reader.Ui32(1) != 0);

            int depth = (int)reader.Ui16();
            int characterId = placeFlagHasCharacter ? (int)reader.Ui16() : 0;

            Matrix matrix = null;
            if (placeFlagHasMatrix)
            {
                matrix = reader.Matrix();
            }

            ColorTransform colorTransform = null;
            if (placeFlagHasColorTransform)
            {
                colorTransform = reader.ColorTransform(true);
            }

            if (placeFlagHasRatio)
            {
                reader.Ui16();
                if (!this.presentedProcessPlaceObject2TagRatioWarning)
                {
                    Log.Warning(Subsystem.LoadingGeneral,
                        "Shape tween ratio not supported by PlaceObject2. This might result in " +
                        "playback issues.");
                    this.presentedProcessPlaceObject2TagRatioWarning = true;
                }
            }

            string name = null;
            if (placeFlagHasName)
            {
                name = reader.String();
            }

            int clipDepth = 0;
            if (placeFlagHasClipDepth)
            {
                clipDepth = reader.Ui16();
            }

            if (placeFlagHasClipActions)
            {
                if (!this.presentedProcessPlaceObject2ClipActionWarning)
                {
                    Log.Warning(Subsystem.LoadingGeneral,
                        "Ignoring clip action events since they are not supported " +
                        "by PlaceObject2 at this time.");
                    this.presentedProcessPlaceObject2ClipActionWarning = true;
                }

                reader.Ui16();    // reserved
                uint flags = (reader.swfVersion >= 6) ? reader.Ui32() : (uint)reader.Ui16(); 

                for (;;)
                {
                    reader.Align();
                    flags = (reader.swfVersion >= 6) ? reader.Ui32() : (uint)reader.Ui16();
                    if (flags == 0)
                    {
                        break;
                    }

                    int recordSize = (int)reader.Ui32();
                    reader.Skip(recordSize);
                }
            }

            clip.timeline.AddActionAt(new MovieClip.PlaceObjectAction(depth, characterId,
                name, matrix, colorTransform, clipDepth, placeFlagMove), currentFrame);
        }

        protected void ProcessRemoveObject2Tag(SwfReader reader, MovieClip clip, int currentFrame)
        {
            int depth = (int)reader.Ui16();
            clip.timeline.AddActionAt(new MovieClip.RemoveObjectAction(depth), currentFrame);
        }

        protected void ProcessDefineEditText(SwfReader reader, int length)
        {
            int id = (int)reader.Ui16();
            Rectangle bounds = reader.Rectangle();

            int flags = reader.Ui8();
            bool hasText = (flags & 0x80) != 0x00;
            bool wordWrap = (flags & 0x40) != 0x00;
            bool multiline = (flags & 0x20) != 0x00;
            bool password = (flags & 0x10) != 0x00;
            bool readOnly = (flags & 0x08) != 0x00;
            bool hasTextColor = (flags & 0x04) != 0x00;
            bool hasMaxLength = (flags & 0x02) != 0x00;
            bool hasFont = (flags & 0x01) != 0x00;

            flags = reader.Ui8();
            bool hasFontClass = (flags & 0x80) != 0x00;
            bool autoSize = (flags & 0x40) != 0x00;
            bool hasLayout = (flags & 0x20) != 0x00;
            bool noSelect = (flags & 0x10) != 0x00;
            bool border = (flags & 0x08) != 0x00;
            // bool wasStatic = (flags & 0x04) != 0x00;
            bool html = (flags & 0x02) != 0x00;
            // bool useOutlines = (flags & 0x01) != 0x00;

            int fontId = hasFont ? reader.Ui16() : 0;
            string fontClass = hasFontClass ? reader.String() : null;
            float fontHeight = hasFont ? reader.Ui16() / 20.0f : 0.0f;
            uint textColor = hasTextColor ? reader.Color(true) : 0xff000000;
            int maxLength = hasMaxLength ? reader.Ui16() : 0;

            if (fontClass != null)
            {
                Log.Warning(Subsystem.LoadingGeneral,
                    "Unsupported usage of a font class by Dynamic Text.");
            }

            int align = 0;
            float leftMargin = 0.0f;
            float rightMargin = 0.0f;
            float indent = 0.0f;
            float leading = 0.0f;
            if (hasLayout)
            {
                align = reader.Ui8();
                leftMargin = reader.Ui16() / 20.0f;
                rightMargin = reader.Ui16() / 20.0f;
                indent = reader.Ui16() / 20.0f;
                leading = reader.Si16() / 20.0f;
            }

            string variableName = reader.String();
            string initialText = hasText ? reader.String() : "";

            TextField tf = new TextField();
            tf.variableName = variableName;

            // TextFields have a 2px margin around them for the background and border
            tf.width = bounds.width - 4.0f;
            tf.height = bounds.height - 4.0f;

            tf.wordWrap = wordWrap;
            tf.multiline = multiline;
            tf.displayAsPassword = password;
            tf.type = readOnly ? TextFieldType.DYNAMIC : TextFieldType.INPUT;
            tf.textColor = textColor;
            tf.maxChars = maxLength;
            tf.autoSize = autoSize ? TextFieldAutoSize.LEFT : TextFieldAutoSize.NONE;
            tf.selectable = !noSelect;
            tf.border = border;
            tf.background = border;

            string[] alignment = { TextFormatAlign.LEFT, TextFormatAlign.RIGHT,
                TextFormatAlign.CENTER, TextFormatAlign.JUSTIFY };

            var font = hasFont ? (context.dictionary[fontId] as Flare.Text.Font) : null;
            TextFormat format = new TextFormat(
                hasFont ? font.fontName : null,
                hasFont ? (float?)fontHeight : null,
                hasTextColor ? (uint?)textColor : null,
                hasFont ? (bool?)(font.fontStyle.Equals(Flare.Text.FontStyle.BOLD) ||
                    font.fontStyle.Equals(Flare.Text.FontStyle.BOLD_ITALIC)) : null,
                hasFont ? (bool?)(font.fontStyle.Equals(Flare.Text.FontStyle.ITALIC) ||
                    font.fontStyle.Equals(Flare.Text.FontStyle.BOLD_ITALIC)) : null,
                hasLayout ? alignment[align] : null,
                hasLayout ? (float?)leftMargin : null,
                hasLayout ? (float?)rightMargin : null,
                hasLayout ? (float?)indent : null,
                hasLayout ? (float?)leading : null);

            tf.loadedTextFormat = format;
            tf.defaultTextFormat = new TextFormat(format);

            if (html)
            {
                // Since text entry is only supported for plain text right now, we'll always
                // default input text fields as plain text
                if (readOnly)
                {
                    tf.htmlText = initialText;
                }
                else
                {
                    tf.text = "";
                }
            }
            else
            {
                tf.text = initialText;
            }

            context.dictionary.Add(id, tf);
        }

        protected void ProcessDefineSpriteTag(SwfReader reader, MovieClip clip)
        {
            int id = (int)reader.Ui16();
            int frameCount = (int)reader.Ui16();

            // Some sprites are defined with zero frames; however, they should have one frame
            if (frameCount < 1)
            {
                frameCount = 1;
            }

            // TODO bwmott 2012/11/07: Consider passing in flag to limit tag reading to the ones
            // valid for sprite definitions
            MovieClip sprite = ReadSwfTags(reader, false, frameCount);

            // Add the defined sprite to the loader context 's dictionary
            context.dictionary.Add(id, sprite);
        }

        protected void ProcessExportAssetsTag(SwfReader reader)
        {
            reader.Align();
            int count = reader.Ui16();
            for (int i = 0; i < count; ++i)
            {
                int id = reader.Ui16();
                string name = reader.String();

                // Add exported symbol to the application domain
                object definition = context.dictionary[id];
                context.applicationDomain.AddExportedSymbol(name, definition);
            }
        }

        protected void ProcessSymbolClassTag(SwfReader reader)
        {
            reader.Align();
            int numSymbols = reader.Ui16();
            for (int i = 0; i < numSymbols; ++i)
            {
                int id = reader.Ui16();
                string name = reader.String(); 

                // Technically, we should check to see that the name is associated with an AS3
                // class declared with DoABC; however, we're not supporting class declarations
                // for now, so we'll just ignore this definition.
                Log.Warning(Subsystem.LoadingGeneral, "Ignoring symbol class definition " +
                    " id={0} name={1}.", id, name);
            }
        }

        protected void ProcessFileAttributesTag(SwfReader reader)
        {
            reader.Align();
            reader.Ui32(1);    // Reserved
            reader.Ui32(1);    // UseDirectBlit
            reader.Ui32(1);    // UseGPU
            reader.Ui32(1);    // HasMetadata
            bool script3 = (reader.Ui32(1) == 1);    // AS3
            reader.Ui32(2);    // Reserved
            reader.Ui32(1);    // UseNetwork
            reader.Ui32(24);   // Reserved

            this.contentLoaderInfo.scriptVersion = script3 ? ScriptVersion.AS3 : ScriptVersion.AS2;
        }

        protected void ProcessFrameLabel(SwfReader reader, int length,
            MovieClip clip, int currentFrame)
        {
            int numBytesRead;

            // Read the label name
            string name = reader.String(out numBytesRead);

            // See if there's a Named Anchor Flag that needs to be read following the string
            if (numBytesRead < length)
            {
                reader.Ui8();
            }

            if ((!clip.mainTimeline) || (!this.m_processedDefineSceneAndFrameLabelDataTag))
            {
                List<FrameLabel> labels = new List<FrameLabel>(clip.scenes[0].labels);
                labels.Add(new FrameLabel(currentFrame + 1, name));
                clip.scenes[0].labels = labels.ToArray();
            }
        }

        protected void ProcessDefineSceneAndFrameLabelDataTag(SwfReader reader, MovieClip clip)
        {
            this.m_processedDefineSceneAndFrameLabelDataTag = true;

            // Read in the list of scene names and offsets
            List<string> sceneNames = new List<string>();
            List<uint> sceneFrameOffset = new List<uint>();

            uint sceneCount = reader.EncodedU32();
            for (uint i = 0; i < sceneCount; ++i)
            {
                uint frameOffset = reader.EncodedU32();
                string name = reader.String();

                sceneNames.Add(name);
                sceneFrameOffset.Add(frameOffset);
            }

            // Read in the list of frame labels
            List<string> labelNames = new List<string>();
            List<uint> labelFrameNum = new List<uint>();

            uint frameLabelCount = reader.EncodedU32();
            for (uint i = 0; i < frameLabelCount; ++i)
            {
                uint frameNum = reader.EncodedU32();
                string name = reader.String();

                labelNames.Add(name);
                labelFrameNum.Add(frameNum);
            }

            // Create list of scene objects and their labels based on the SWF data
            List<Scene> scenes = new List<Scene>();
            for (int i = 0; i < sceneNames.Count; ++i)
            {
                int nextSceneFrame = (i < (sceneNames.Count - 1)) ?
                    (int)sceneFrameOffset[i + 1] : this.contentLoaderInfo.totalFrames;

                int numFrames = nextSceneFrame - (int)sceneFrameOffset[i];

                // Create list of FrameLabel objects for this scene
                List<FrameLabel> labels = new List<FrameLabel>();
                for (int j = 0; j < labelNames.Count; ++j)
                {
                    if ((labelFrameNum[j] >= sceneFrameOffset[i]) &&
                        (labelFrameNum[j] < nextSceneFrame))
                    {
                        FrameLabel label = new FrameLabel(
                            ((int)labelFrameNum[j] - (int)sceneFrameOffset[i]) + 1, labelNames[j]);
                        labels.Add(label);
                    }
                }

                scenes.Add(new Scene(sceneNames[i], numFrames, labels.ToArray()));
            }

            // Add the list of scenes to the movie clip
            clip.scenes = scenes.ToArray();
        }

        // ADPCM Index and Step Size Tables
        private readonly static int[,] adpcmIndexTable = new int[4, 16] {
            {-1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2, -1, 2},    // 2-bits
            {-1, -1, 2, 4, -1, -1, 2, 4, -1, -1, 2, 4, -1, -1, 2, 4},    // 3-bits
            {-1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8},    // 4-bits
            {-1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 4, 6, 8, 10, 13, 16}  // 5-bits
        };
        private readonly static int[] adpcmStepSizeTable = {
            7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34,
            37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143,
            157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494,
            544, 598, 658, 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552,
            1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428,
            4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487,
            12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086,
            29794, 32767 };

        protected void ProcessDefineSound(SwfReader reader, int length,
            MovieClip clip, int currentFrame)
        {
            // Read in the DefineSound tag
            int id = (int)reader.Ui16();

            reader.Align();

            int soundFormat = (int)reader.Ui32(4);
            int soundRate = (int)reader.Ui32(2);
            int soundSize = (int)reader.Ui32(1);   // 0 = 8bit, 1 = 16bit
            int soundType = (int)reader.Ui32(1);   // 0 = mono, 1 = stereo
            int soundSampleCount = (int)reader.Ui32();

            int frequency = (int)(5512.5f * (1 << soundRate));
            float[] data = new float[soundSampleCount * (soundType + 1)];

            // ADPCM Compressed Data
            if (soundFormat == 1)
            {
                // Read the ADPCM data into a byte array for decompression and pad it with an
                // additional byte since Flash appears to sometimes output fewer bits than
                // necessary. By moving this into another SwfReader we avoid reading past the
                // end of the DefineSound tag.
                byte[] adpcmData = new byte[(length - 7) + 1];
                reader.Read(adpcmData, 0, (length - 7));
                SwfReader adpcmReader = new SwfReader(new MemoryStream(adpcmData));

                // Read in codeSize
                int codeSize = (int)adpcmReader.Ui32(2) + 2;
                int codeHighBit = 1 << (codeSize - 1);

                // Mono ADPCM
                if (soundType == 0)
                {
                    // Read in Mono ADPCM packets
                    for (int samplesRead = 0; samplesRead < soundSampleCount; )
                    {
                        // Read in initial sample and index for this packet
                        int prevSample = adpcmReader.Si32(16);
                        int index = (int)adpcmReader.Ui32(6);
                        data[samplesRead++] = prevSample / 32768.0f;

                        int samplesToProcess = Mathf.Min(4095, soundSampleCount - samplesRead);
                        for (int i = 0; i < samplesToProcess; i++)
                        {
                            int code = (int)adpcmReader.Ui32(codeSize);
                            int stepsize = adpcmStepSizeTable[index];
                            int difference = (stepsize * (((code & (codeHighBit - 1)) << 1) + 1))
                                >> (codeSize - 1);

                            // Update sample based on the difference
                            prevSample += (((code & codeHighBit) == 0) ? difference : -difference);
                            prevSample = Mathf.Clamp(prevSample, -32768, 32767);

                            // Find the next index for the StepSize Table
                            index += adpcmIndexTable[codeSize - 2, code & 0x0f];
                            index = Mathf.Clamp(index, 0, 88);

                            data[samplesRead++] = prevSample / 32768.0f;
                        }
                    }
                }
                // Stereo ADPCM
                else
                {
                    // Read in Stereo ADPCM packets
                    for (int samplesRead = 0; samplesRead < 2 * soundSampleCount; )
                    {
                        // Read in initial left sample and index for this packet
                        int prevLeftSample = adpcmReader.Si32(16);
                        int leftIndex = (int)adpcmReader.Ui32(6);
                        data[samplesRead++] = prevLeftSample / 32768.0f;

                        // Read in initial right sample and index for this packet
                        int prevRightSample = adpcmReader.Si32(16);
                        int rightIndex = (int)adpcmReader.Ui32(6);
                        data[samplesRead++] = prevRightSample / 32768.0f;

                        int samplesToProcess = Mathf.Min(4095,
                            soundSampleCount - (samplesRead / 2));
                        for (int i = 0; i < samplesToProcess; i++)
                        {
                            int code = (int)adpcmReader.Ui32(codeSize);
                            int stepsize = adpcmStepSizeTable[leftIndex];
                            int difference = (stepsize * (((code & (codeHighBit - 1)) << 1) + 1))
                                >> (codeSize - 1);

                            // Update left sample based on the difference
                            prevLeftSample += (((code & codeHighBit) == 0) ?
                                difference : -difference);
                            prevLeftSample = Mathf.Clamp(prevLeftSample, -32768, 32767);

                            // Find the next left index for the StepSize Table
                            leftIndex += adpcmIndexTable[codeSize - 2, code];
                            leftIndex = Mathf.Clamp(leftIndex, 0, 88);

                            code = (int)adpcmReader.Ui32(codeSize);
                            stepsize = adpcmStepSizeTable[rightIndex];
                            difference = (stepsize * (((code & (codeHighBit - 1)) << 1) + 1))
                                >> (codeSize - 1);

                            // Update right sample based on the difference
                            prevRightSample += (((code & codeHighBit) == 0) ?
                                difference : -difference);
                            prevRightSample = Mathf.Clamp(prevRightSample, -32768, 32767);

                            // Find the next right index for the StepSize Table
                            rightIndex += adpcmIndexTable[codeSize - 2, code];
                            rightIndex = Mathf.Clamp(rightIndex, 0, 88);

                            data[samplesRead++] = prevLeftSample / 32768.0f;
                            data[samplesRead++] = prevRightSample / 32768.0f;
                        }
                    }
                }
            }
            // Uncompressed Little-endian Data
            else if (soundFormat == 3)
            {
                // Read raw samples and pad them with 8 additional bytes since Flash appears to
                // sometimes output fewer bytes than necessary.
                byte[] rawSamples = new byte[length - 7 + 8];
                reader.Read(rawSamples, 0, length - 7);

                // 8-bit data (mono & stereo)
                if (soundSize == 0)
                {
                    for (int i = 0; i < soundSampleCount * (soundType + 1); i++)
                    {
                        data[i] = (sbyte)(rawSamples[i]) / 128.0f;
                    }
                }
                // 16-bit data (mono & stereo)
                else
                {
                    for (int i = 0; i < soundSampleCount * (soundType + 1); i++)
                    {
                        short sample = (short)((int)rawSamples[i * 2] +
                            ((int)rawSamples[i * 2 + 1] << 8));
                        data[i] = sample / 32768.0f;
                    }
                }
            }
            else
            {
                Log.Error(Subsystem.LoadingGeneral, "Unsupported Audio Format: {0}", soundFormat);
                reader.Skip(length - 7);
            }

            // Create the AudioClip, set its data, and add it as a Sound to the dictionary
            AudioClip soundClip = AudioClip.Create(id.ToString(), soundSampleCount, soundType + 1,
#if UNITY_5
                frequency, false);
#else
                frequency, false, false);
#endif
            soundClip.hideFlags = HideFlags.HideAndDontSave;
            soundClip.SetData(data, 0);
            context.dictionary.Add(id, new Sound(soundClip));
        }

        protected void ProcessStartSound(SwfReader reader, MovieClip clip, int currentFrame)
        {
            int soundID = reader.Ui16();
            reader.Align();
            
            // Read in SoundInfo
            int data = (int)reader.Ui32(8);
            bool syncStop = ((data & 0x20) != 0);
            bool syncNoMultiple = ((data & 0x10) != 0);
            bool hasEnvelope = ((data & 0x08) != 0);
            bool hasLoops = ((data & 0x04) != 0);
            bool hasOutPoint = ((data & 0x02) != 0);
            bool hasInPoint = ((data & 0x01) != 0);

            int inPoint = hasInPoint ? (int)reader.Ui32() : 0;
            int outPoint = hasOutPoint ? (int)reader.Ui32() : 0;
            int loopCount = hasLoops ? (int)reader.Ui16() : 0;

            // Check for SoundEnvelope
            if (hasEnvelope)
            {
                Log.Error(Subsystem.LoadingGeneral,
                    "Sound Envelopes are not supported for sounds!");

                // Discard SoundEnvelope
                int envPoints = (int)reader.Ui8();
                for (int i = 0; i < envPoints; i++)
                {
                    reader.Ui32();
                    reader.Ui16();
                    reader.Ui16();
                }
            }

            // Create and add a new SoundAction
            clip.timeline.AddActionAt(new MovieClip.SoundAction(soundID, syncStop, syncNoMultiple,
                inPoint, outPoint, loopCount), currentFrame);
        }

        protected void ProcessSoundStreamHead(SwfReader reader, int tag, int length, MovieClip clip,
            int currentFrame)
        {
            reader.Align();

            reader.Ui32(4);                            // 4 reserved bits
            int playbackRate = (int)reader.Ui32(2);    // Playback sampling rate
            int playbackSize = (int)reader.Ui32(1);    // Playback sample size (0 = 8bit, 1 = 16bit)
            int playbackType = (int)reader.Ui32(1);    // Playback channels (0 = mono, 1 = stereo)
            int streamFormat = (int)reader.Ui32(4);    // APDCM, MP3, Uncompressed, etc.
            int streamRate = (int)reader.Ui32(2);      // Stream sound data sample rate
            int streamSize = (int)reader.Ui32(1);      // Stream sample size (0 = 8bit, 1 = 16bit)
            int streamType = (int)reader.Ui32(1) + 1;  // Stream channels (0 = mono, 1 = stereo)
            int sampleCount = (int)reader.Ui16();      // Average samples in each stream block
            if (streamFormat == 2)
            {
                reader.Si16();                         // Latency seek for MP3 (not used)
            }

            int streamFrequency = (int)(5512.5f * (1 << streamRate));

            // Initialize the clip's streamSound member
            clip.streamSound = new SoundStream(clip, playbackRate, playbackSize, playbackType,
                streamFormat, streamFrequency, streamSize, streamType, sampleCount);

            reader.Align();
        }

        protected void ProcessSoundStreamBlock(SwfReader reader, int length, MovieClip clip,
            int currentFrame)
        {
            // Check for previous SoundStreamHead
            if(clip.streamSound == null)
            {
                reader.Skip(length);
                Log.Error(Subsystem.LoadingGeneral,"No preceding SoundStreamBlock!");
                return;
            }

            Queue<float> data = new Queue<float>();
            reader.Align();

            // Read in SoundData based on format
            switch(clip.streamSound.format)
            {
                // Read in ADPCM data
                case 1:
                    // Index Tables
                    int[] IndexTable2 = {-1, 2};
                    int[] IndexTable3 = {-1, -1, 2, 4};
                    int[] IndexTable4 = {-1, -1, -1, -1, 2, 4, 6, 8};
                    int[] IndexTable5 = {-1, -1, -1, -1, -1, -1, -1, -1, 1, 2, 4, 6, 8, 10, 13, 16};

                    // Step size table
                    int[] StepSizeTable = {7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28,
                    31, 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157,
                    173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724,
                    796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749,
                    3024, 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493,
                    10442, 11487, 12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086,
                    29794, 32767};

                    // Read in codeSize
                    int codeSize = (int)reader.Ui32(2) + 2;

                    int bitsLeft = length * 8 - 2;

                    // Check for mono or stereo
                    if(clip.streamSound.channels == 1)
                    {

                        // Read in ADPCM packets
                        while(bitsLeft > 0)
                        {
                            // Read in initial sample
                            short prevSample = (short)reader.Si32(16);
                            data.Enqueue((prevSample + 32768f) /65535f);

                            // Read in index
                            int index = (int)reader.Ui32(6);

                            bitsLeft -= 22;

                            switch(codeSize)
                            {
                                case 2:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 1; i++)
                                    {
                                        int sample = (int)reader.Ui32(2);
                                        bitsLeft -= 2;

                                        short difference = 0;
                                        int sign = sample & 0x2;
                                        int code = sample & 0x1;

                                        // Get new step size
                                        int stepsize = StepSizeTable[index];

                                        // Calculate the difference
                                        if((code & 0x1) == 0x1)
                                        {
                                            difference += (short) stepsize;
                                        }
                                        difference += (short) (stepsize >> 1);

                                        if(sign != 0)
                                        {
                                            difference = (short) -difference;
                                        }

                                        // Changes newSample based on the difference
                                         prevSample += difference;

                                        // Clamps the sample
                                        if(prevSample > 32767)
                                        {
                                            prevSample = 32767;
                                        }
                                        else if(prevSample < -32768)
                                        {
                                            prevSample = -32768;
                                        }

                                        // Finds the next index for the StepSize Table
                                        index += IndexTable2[code];

                                        // Clamps the index
                                        if(index < 0)
                                        {
                                            index = 0;
                                        }
                                        else if(index > 88)
                                        {
                                            index = 88;
                                        }

                                        data.Enqueue((prevSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 2)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                                case 3:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 2; i++)
                                    {
                                        int sample = (int)reader.Ui32(3);
                                        bitsLeft -= 3;

                                        short difference = 0;
                                        int sign = sample & 0x4;
                                        int code = sample & 0x3;

                                        // Get new step size
                                        int stepsize = StepSizeTable[index];

                                        // Calculate the difference
                                        if((code & 0x2) == 0x2)
                                        {
                                            difference += (short) stepsize;
                                        }
                                        if((code & 0x1) == 0x1)
                                        {
                                            difference += (short) (stepsize >> 1);
                                        }
                                        difference += (short) (stepsize >> 2);

                                        if(sign != 0)
                                        {
                                            difference = (short) -difference;
                                        }

                                        // Changes newSample based on the difference
                                        prevSample += difference;

                                        // Clamps the sample
                                        if(prevSample > 32767)
                                        {
                                            prevSample = 32767;
                                        }
                                        else if(prevSample < -32768)
                                        {
                                            prevSample = -32768;
                                        }

                                        // Finds the next index for the StepSize Table
                                        index += IndexTable3[code];

                                        // Clamps the index
                                        if(index < 0)
                                        {
                                            index = 0;
                                        }
                                        else if(index > 88)
                                        {
                                            index = 88;
                                        }

                                        data.Enqueue((prevSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 3)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                                case 4:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 3; i++)
                                    {
                                        int sample = (int)reader.Ui32(4);
                                        bitsLeft -= 4;

                                        short difference = 0;
                                        int sign = sample & 0x8;
                                        int code = sample & 0x7;

                                        // Get new step size
                                        int stepsize = StepSizeTable[index];

                                        // Calculate the difference
                                        if((code & 0x4) == 0x4)
                                        {
                                            difference += (short) stepsize;
                                        }
                                        if((code & 0x2) == 0x2)
                                        {
                                            difference += (short) (stepsize >> 1);
                                        }
                                        if((code & 0x1) == 0x1)
                                        {
                                            difference += (short) (stepsize >> 2);
                                        }
                                        difference += (short) (stepsize >> 3);

                                        if(sign != 0)
                                        {
                                            difference = (short) -difference;
                                        }

                                        // Changes newSample based on the difference
                                        prevSample += difference;

                                        // Clamps the sample
                                        if(prevSample > 32767)
                                        {
                                            prevSample = 32767;
                                        }
                                        else if(prevSample < -32768)
                                        {
                                            prevSample = -32768;
                                        }

                                        // Finds the next index for the StepSize Table
                                        index += IndexTable4[code];

                                        // Clamps the index
                                        if(index < 0)
                                        {
                                            index = 0;
                                        }
                                        else if(index > 88)
                                        {
                                            index = 88;
                                        }

                                        data.Enqueue((prevSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 4)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                                case 5:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 4; i++)
                                    {
                                        int sample = (int)reader.Ui32(5);
                                        bitsLeft -= 5;

                                        short difference = 0;
                                        int sign = sample & 0x10;
                                        int code = sample & 0xf;

                                        // Get new step size
                                        int stepsize = StepSizeTable[index];

                                        // Calculate the difference
                                        if((code & 0x8) == 0x8)
                                        {
                                            difference += (short) stepsize;
                                        }
                                        if((code & 0x4) == 0x4)
                                        {
                                            difference += (short) (stepsize >> 1);
                                        }
                                        if((code & 0x2) == 0x2)
                                        {
                                            difference += (short) (stepsize >> 2);
                                        }
                                        if((code & 0x1) == 0x1)
                                        {
                                            difference += (short) (stepsize >> 3);
                                        }
                                        difference += (short) (stepsize >> 4);

                                        if(sign != 0)
                                        {
                                            difference = (short) -difference;
                                        }

                                        // Changes newSample based on the difference
                                        prevSample += difference;

                                        // Clamps the sample
                                        if(prevSample > 32767)
                                        {
                                            prevSample = 32767;
                                        }
                                        else if(prevSample < -32768)
                                        {
                                            prevSample = -32768;
                                        }

                                        // Finds the next index for the StepSize Table
                                        index += IndexTable5[code];

                                        // Clamps the index
                                        if(index < 0)
                                        {
                                            index = 0;
                                        }
                                        else if(index > 88)
                                        {
                                            index = 88;
                                        }

                                        data.Enqueue((prevSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 5)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                            }
                        }
                    }
                    else
                    {
                        // Read in ADPCM packets
                        while(bitsLeft > 0)
                        {
                            // Read in initial sample
                            int prevLeftSample = reader.Si32(16);
                            data.Enqueue((prevLeftSample + 32768f) /65535f);

                            // Read in index
                            int leftIndex = (int)reader.Ui32(6);

                            // Read in initial sample
                            int prevRightSample = reader.Si32(16);
                            data.Enqueue((prevRightSample + 32768f) /65535f);

                            // Read in index
                            int rightIndex = (int)reader.Ui32(6);

                            bitsLeft -= 44;

                            switch(codeSize)
                            {
                                case 2:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 3; i++)
                                    {
                                        int leftSample = (int)reader.Ui32(2);
                                        int rightSample = (int)reader.Ui32(2);
                                        bitsLeft -= 4;

                                        short leftDifference = 0;
                                        short rightDifference = 0;
                                        int leftSign = leftSample & 0x2;
                                        int rightSign = rightSample & 0x2;
                                        int leftCode = leftSample & 0x1;
                                        int rightCode = rightSample & 0x1;

                                        // Get new step sizes
                                        int leftStepSize = StepSizeTable[leftIndex];
                                        int rightStepSize = StepSizeTable[rightIndex];

                                        // Calculates the differences
                                        if((leftCode & 0x1) == 0x1)
                                        {
                                            leftDifference += (short) leftStepSize;
                                        }
                                        leftDifference += (short) (leftStepSize >> 1);

                                        if(leftSign != 0)
                                        {
                                            leftDifference = (short) -leftDifference;
                                        }

                                        if((rightCode & 0x1) == 0x1)
                                        {
                                            rightDifference += (short) rightStepSize;
                                        }
                                        rightDifference += (short) (rightStepSize >> 1);

                                        if(rightSign != 0)
                                        {
                                            rightDifference = (short) -rightDifference;
                                        }

                                        // Changes new samples based on the differences
                                        prevLeftSample += leftDifference;
                                        prevRightSample += rightDifference;

                                        // Clamps the samples
                                        if(prevLeftSample > 32767)
                                        {
                                            prevLeftSample = 32767;
                                        }
                                        else if(prevLeftSample < -32768)
                                        {
                                            prevLeftSample = -32768;
                                        }

                                        if(prevRightSample > 32767)
                                        {
                                            prevRightSample = 32767;
                                        }
                                        else if(prevRightSample < -32768)
                                        {
                                            prevRightSample = -32768;
                                        }

                                        // Finds the next indexes for the StepSize Table
                                        leftIndex += IndexTable2[leftCode];
                                        rightIndex += IndexTable2[rightCode];

                                        // Clamps the indexes
                                        if(leftIndex < 0)
                                        {
                                            leftIndex = 0;
                                        }
                                        else if(leftIndex > 88)
                                        {
                                            leftIndex = 88;
                                        }

                                        if(rightIndex < 0)
                                        {
                                            rightIndex = 0;
                                        }
                                        else if(rightIndex > 88)
                                        {
                                            rightIndex = 88;
                                        }

                                        // Store the decoded samples
                                        data.Enqueue((prevLeftSample + 32768f) /65535f);
                                        data.Enqueue((prevRightSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 4)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                                case 3:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 5; i++)
                                    {
                                        int leftSample = (int)reader.Ui32(3);
                                        int rightSample = (int)reader.Ui32(3);
                                        bitsLeft -= 6;

                                        short leftDifference = 0;
                                        short rightDifference = 0;
                                        int leftSign = leftSample & 0x4;
                                        int rightSign = rightSample & 0x4;
                                        int leftCode = leftSample & 0x3;
                                        int rightCode = rightSample & 0x3;

                                        // Get new step sizes
                                        int leftStepSize = StepSizeTable[leftIndex];
                                        int rightStepSize = StepSizeTable[rightIndex];

                                        // Calculates the differences
                                        if((leftCode & 0x2) == 0x2)
                                        {
                                            leftDifference += (short) leftStepSize;
                                        }
                                        if((leftCode & 0x1) == 0x1)
                                        {
                                            leftDifference += (short) (leftStepSize >> 1);
                                        }
                                        leftDifference += (short) (leftStepSize >> 2);

                                        if(leftSign != 0)
                                        {
                                            leftDifference = (short) -leftDifference;
                                        }

                                        if((rightCode & 0x2) == 0x2)
                                        {
                                            rightDifference += (short) rightStepSize;
                                        }
                                        if((rightCode & 0x1) == 0x1)
                                        {
                                            rightDifference += (short) (rightStepSize >> 1);
                                        }
                                        rightDifference += (short) (rightStepSize >> 2);

                                        if(rightSign != 0)
                                        {
                                            rightDifference = (short) -rightDifference;
                                        }

                                        // Changes new samples based on the differences
                                        prevLeftSample += leftDifference;
                                        prevRightSample += rightDifference;

                                        // Clamps the samples
                                        if(prevLeftSample > 32767)
                                        {
                                            prevLeftSample = 32767;
                                        }
                                        else if(prevLeftSample < -32768)
                                        {
                                            prevLeftSample = -32768;
                                        }

                                        if(prevRightSample > 32767)
                                        {
                                            prevRightSample = 32767;
                                        }
                                        else if(prevRightSample < -32768)
                                        {
                                            prevRightSample = -32768;
                                        }

                                        // Finds the next indexes for the StepSize Table
                                        leftIndex += IndexTable3[leftCode];
                                        rightIndex += IndexTable3[rightCode];

                                        // Clamps the indexes
                                        if(leftIndex < 0)
                                        {
                                            leftIndex = 0;
                                        }
                                        else if(leftIndex > 88)
                                        {
                                            leftIndex = 88;
                                        }

                                        if(rightIndex < 0)
                                        {
                                            rightIndex = 0;
                                        }
                                        else if(rightIndex > 88)
                                        {
                                            rightIndex = 88;
                                        }

                                        // Store the decoded samples
                                        data.Enqueue((prevLeftSample + 32768f) /65535f);
                                        data.Enqueue((prevRightSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 6)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                                case 4:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 7; i++)
                                    {
                                        int leftSample = (int)reader.Ui32(4);
                                        int rightSample = (int)reader.Ui32(4);
                                        bitsLeft -= 8;

                                        short leftDifference = 0;
                                        short rightDifference = 0;
                                        int leftSign = leftSample & 0x8;
                                        int rightSign = rightSample & 0x8;
                                        int leftCode = leftSample & 0x7;
                                        int rightCode = rightSample & 0x7;

                                        // Get new step sizes
                                        int leftStepSize = StepSizeTable[leftIndex];
                                        int rightStepSize = StepSizeTable[rightIndex];

                                        // Calculates the differences
                                        if((leftCode & 0x4) == 0x4)
                                        {
                                            leftDifference += (short) leftStepSize;
                                        }
                                        if((leftCode & 0x2) == 0x2)
                                        {
                                            leftDifference += (short) (leftStepSize >> 1);
                                        }
                                        if((leftCode & 0x1) == 0x1)
                                        {
                                            leftDifference += (short) (leftStepSize >> 2);
                                        }
                                        leftDifference += (short) (leftStepSize >> 3);

                                        if(leftSign != 0)
                                        {
                                            leftDifference = (short) -leftDifference;
                                        }

                                        if((rightCode & 0x4) == 0x4)
                                        {
                                            rightDifference += (short) rightStepSize;
                                        }
                                        if((rightCode & 0x2) == 0x2)
                                        {
                                            rightDifference += (short) (rightStepSize >> 1);
                                        }
                                        if((rightCode & 0x1) == 0x1)
                                        {
                                            rightDifference += (short) (rightStepSize >> 2);
                                        }
                                        rightDifference += (short) (rightStepSize >> 3);

                                        if(rightSign != 0)
                                        {
                                            rightDifference = (short) -rightDifference;
                                        }

                                        // Changes new samples based on the differences
                                        prevLeftSample += leftDifference;
                                        prevRightSample += rightDifference;

                                        // Clamps the samples
                                        if(prevLeftSample > 32767)
                                        {
                                            prevLeftSample = 32767;
                                        }
                                        else if(prevLeftSample < -32768)
                                        {
                                            prevLeftSample = -32768;
                                        }

                                        if(prevRightSample > 32767)
                                        {
                                            prevRightSample = 32767;
                                        }
                                        else if(prevRightSample < -32768)
                                        {
                                            prevRightSample = -32768;
                                        }

                                        // Finds the next indexes for the StepSize Table
                                        leftIndex += IndexTable4[leftCode];
                                        rightIndex += IndexTable4[rightCode];

                                        // Clamps the indexes
                                        if(leftIndex < 0)
                                        {
                                            leftIndex = 0;
                                        }
                                        else if(leftIndex > 88)
                                        {
                                            leftIndex = 88;
                                        }

                                        if(rightIndex < 0)
                                        {
                                            rightIndex = 0;
                                        }
                                        else if(rightIndex > 88)
                                        {
                                            rightIndex = 88;
                                        }

                                        // Store the decoded samples
                                        data.Enqueue((prevLeftSample + 32768f) /65535f);
                                        data.Enqueue((prevRightSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 8)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                                case 5:
                                    // Begin decoding
                                    for(int i = 0; i < 4095 && bitsLeft > 9; i++)
                                    {
                                        int leftSample = (int)reader.Ui32(5);
                                        int rightSample = (int)reader.Ui32(5);
                                        bitsLeft -= 10;

                                        short leftDifference = 0;
                                        short rightDifference = 0;
                                        int leftSign = leftSample & 0x10;
                                        int rightSign = rightSample & 0x10;
                                        int leftCode = leftSample & 0xf;
                                        int rightCode = rightSample & 0xf;

                                        // Get new step sizes
                                        int leftStepSize = StepSizeTable[leftIndex];
                                        int rightStepSize = StepSizeTable[rightIndex];

                                        // Calculates the differences
                                        if((leftCode & 0x8) == 0x8)
                                        {
                                            leftDifference += (short) leftStepSize;
                                        }
                                        if((leftCode & 0x4) == 0x4)
                                        {
                                            leftDifference += (short) (leftStepSize >> 1);
                                        }
                                        if((leftCode & 0x2) == 0x2)
                                        {
                                            leftDifference += (short) (leftStepSize >> 2);
                                        }
                                        if((leftCode & 0x1) == 0x1)
                                        {
                                            leftDifference += (short) (leftStepSize >> 3);
                                        }
                                        leftDifference += (short) (leftStepSize >> 4);

                                        if(leftSign != 0)
                                        {
                                            leftDifference = (short) -leftDifference;
                                        }

                                        if((rightCode & 0x8) == 0x8)
                                        {
                                            rightDifference += (short) rightStepSize;
                                        }
                                        if((rightCode & 0x4) == 0x4)
                                        {
                                            rightDifference += (short) (rightStepSize >> 1);
                                        }
                                        if((rightCode & 0x2) == 0x2)
                                        {
                                            rightDifference += (short) (rightStepSize >> 2);
                                        }
                                        if((rightCode & 0x1) == 0x1)
                                        {
                                            rightDifference += (short) (rightStepSize >> 3);
                                        }
                                        rightDifference += (short) (rightStepSize >> 4);

                                        if(rightSign != 0)
                                        {
                                            rightDifference = (short) -rightDifference;
                                        }

                                        // Changes new samples based on the differences
                                        prevLeftSample += leftDifference;
                                        prevRightSample += rightDifference;

                                        // Clamps the samples
                                        if(prevLeftSample > 32767)
                                        {
                                            prevLeftSample = 32767;
                                        }
                                        else if(prevLeftSample < -32768)
                                        {
                                            prevLeftSample = -32768;
                                        }

                                        if(prevRightSample > 32767)
                                        {
                                            prevRightSample = 32767;
                                        }
                                        else if(prevRightSample < -32768)
                                        {
                                            prevRightSample = -32768;
                                        }

                                        // Finds the next indexes for the StepSize Table
                                        leftIndex += IndexTable5[leftCode];
                                        rightIndex += IndexTable5[rightCode];

                                        // Clamps the indexes
                                        if(leftIndex < 0)
                                        {
                                            leftIndex = 0;
                                        }
                                        else if(leftIndex > 88)
                                        {
                                            leftIndex = 88;
                                        }

                                        if(rightIndex < 0)
                                        {
                                            rightIndex = 0;
                                        }
                                        else if(rightIndex > 88)
                                        {
                                            rightIndex = 88;
                                        }

                                        // Store the decoded samples
                                        data.Enqueue((prevLeftSample + 32768f) /65535f);
                                        data.Enqueue((prevRightSample + 32768f) /65535f);
                                    }

                                    // Remove byte padding
                                    if(bitsLeft < 10)
                                    {
                                        reader.Ui32(bitsLeft);
                                        bitsLeft = 0;
                                    }

                                    break;
                            }

                        }
                    }
                    reader.Align();
                    break;

                // Read in uncompressed little-edian data
                case 3:
                    // Check the sound size
                    if(clip.streamSound.soundSize == 0)
                    {
                        for(int i = 0; i < length; i++)
                        {
                            data.Enqueue(reader.Ui8() / 255f);
                        }
                    }
                    else
                    {
                        for(int i = 0; i < length / 2; i++)
                        {
                            data.Enqueue(((short)reader.Si16() + 32768f) / 65535f);
                        }
                    }
                    break;

                // Outputs error message for unsupported audio formats and skips the tag
                default:
                        reader.Skip(length);
                        Log.Error(Subsystem.LoadingGeneral,"Unsupported Audio Format: {0}",
                            clip.streamSound.format);
                        return;

            }

            // Counts the number samples for all the SoundStreamBlocks
            clip.streamSound.sampleCount += data.Count;

            // Create and add a new SoundStreamAction
            clip.timeline.AddActionAt(new MovieClip.SoundStreamAction(data.ToArray()),
                currentFrame);
        }

        protected sealed class SwfReader
        {
            internal global::System.IO.Stream stream;
            internal uint swfVersion { get; set; }

            private byte[] buf = new byte[1024];
            private int bitCount = 0;
            private int bitBuf = 0;

            public SwfReader(global::System.IO.Stream stream)
            {
                this.stream = stream;
                this.swfVersion = 1;
            }
           
            public void Align()
            {
                this.bitCount = 0;
                this.bitBuf = 0;
            }

            public void UseZlibDecompression()
            {
                this.stream = new ZlibStream(stream, CompressionMode.Decompress);
            }

            public void Skip(int length)
            {
                while (length > 0)
                {
                    this.stream.Read(buf, 0, (length > 1024) ? 1024 : length);
                    length -= 1024;
                }
            }

            public int Si8()
            {
                stream.Read(buf, 0, 1);
                return (sbyte)buf[0];
            }

            public int Ui8()
            {
                stream.Read(buf, 0, 1);
                return (int)buf[0];
            }

            public int Ui16()
            {
                stream.Read(buf, 0, 2);
                return ((int)buf[0]) | ((int)buf[1] << 8);
            }

            public int Si16()
            {
                stream.Read(buf, 0, 2);
                return ((short)buf[0]) | ((short)buf[1] << 8);
            }

            public int Si32()
            {
                stream.Read(buf, 0, 4);
                return ((int)buf[0]) | ((int)buf[1] << 8) | ((int)buf[2] << 16) |
                    ((int)buf[3] << 24);
            }

            public uint Ui32()
            {
                stream.Read(buf, 0, 4);
                return ((uint)buf[0]) | ((uint)buf[1] << 8) | ((uint)buf[2] << 16) |
                    ((uint)buf[3] << 24);
            }

            public float Fixed8()
            {
                return (this.Si16() / 256.0f);
            }

            public int Si32(int bits)
            {
                int value = (int)Ui32(bits);
                if ((value & (1 << (bits - 1))) != 0)
                {
                    value |= -1 << bits;
                }

                return value;
            }

            public uint Ui32(int bits)
            {
                uint result = 0;

                if (bits > 0)
                {
                    // Fill bit buffer if it's empty
                    if (this.bitCount == 0)
                    {
                        this.bitBuf = (byte)stream.ReadByte();
                        this.bitCount = 8;
                    }

                    for(;;)
                    {
                        if (bits > this.bitCount)
                        {
                            result |= (uint)(this.bitBuf << (bits - this.bitCount));
                            bits -= this.bitCount;

                            // Fill bit buffer
                            this.bitBuf = (byte)stream.ReadByte();
                            this.bitCount = 8;
                        }
                        else
                        {
                            result |= (uint)(this.bitBuf >> (this.bitCount - bits));
                            this.bitBuf &= ((1 << (this.bitCount - bits)) - 1);
                            this.bitCount -= bits;

                            break;
                        }
                    }
                }

                return result;
            }

            public uint EncodedU32()
            {
                this.Align();

                stream.Read(buf, 0, 1);
                uint result = buf[0];
                if ((result & 0x00000080) == 0)
                {
                    return result;
                }

                stream.Read(buf, 0, 1);
                result = (result & 0x0000007f) | ((uint)buf[0] << 7);
                if ((result & 0x00004000) == 0)
                {
                    return result;
                }

                stream.Read(buf, 0, 1);
                result = (result & 0x00003fff) | ((uint)buf[0] << 14);
                if ((result & 0x00200000) == 0)
                {
                    return result;
                }

                stream.Read(buf, 0, 1);
                result = (result & 0x001fffff) | ((uint)buf[0] << 21);
                if ((result & 0x10000000) == 0)
                {
                    return result;
                }

                stream.Read(buf, 0, 1);
                result = (result & 0x0fffffff) | ((uint)buf[0] << 28);
                return result;
            }

            public ColorTransform ColorTransform(bool readAlpha)
            {
                this.Align();

                bool hasAddTerms = ((int)Ui32(1) != 0);
                bool hasMultTerms = ((int)Ui32(1) != 0);
                int nbits = (int)Ui32(4);

                float rm = 1.0f, gm = 1.0f, bm = 1.0f, am = 1.0f;
                if (hasMultTerms)
                {
                    rm = (float)Si32(nbits) / 256.0f;
                    gm = (float)Si32(nbits) / 256.0f;
                    bm = (float)Si32(nbits) / 256.0f;
                    am = readAlpha ? ((float)Si32(nbits) / 256.0f) : 1.0f;
                }

                float ra = 0.0f, ga = 0.0f, ba = 0.0f, aa = 0.0f;
                if (hasAddTerms)
                {
                    ra = (float)Si32(nbits);
                    ga = (float)Si32(nbits);
                    ba = (float)Si32(nbits);
                    aa = readAlpha ? ((float)Si32(nbits)) : 0.0f;
                }

                return new ColorTransform(rm, gm, bm, am, ra, ga, ba, aa);
            }

            /// <summary>
            /// Read in color record as a 32-bit unsigned integer encoded as 0xAARRGGBB
            /// </summary>
            public uint Color(bool readAlpha)
            {
                this.Align();

                if (readAlpha)
                {
                    stream.Read(buf, 0, 4);
                    return ((uint)buf[2]) | ((uint)buf[1] << 8) | ((uint)buf[0] << 16) |
                        ((uint)buf[3] << 24);
                }
                else
                {
                    stream.Read(buf, 0, 3);
                    return ((uint)buf[2]) | ((uint)buf[1] << 8) | ((uint)buf[0] << 16) |
                        0xFF000000;
                }
            }

            public Matrix Matrix()
            {
                this.Align();

                Matrix mat = new Matrix();

                bool hasScale = ((int)Ui32(1) != 0);
                if (hasScale)
                {
                    int nbits = (int)Ui32(5);

                    mat.a = (float)Si32(nbits) / 65536.0f;
                    mat.d = (float)Si32(nbits) / 65536.0f;
                }
                bool hasRotation = ((int)Ui32(1) != 0);
                if (hasRotation)
                {
                    int nbits = (int)Ui32(5);
                    mat.b = (float)Si32(nbits) / 65536.0f;
                    mat.c = (float)Si32(nbits) / 65536.0f;
                }

                int nTranslateBits = (int)Ui32(5);
                mat.tx = (float)Si32(nTranslateBits) / 20.0f;
                mat.ty = (float)Si32(nTranslateBits) / 20.0f;

                return mat;
            }

            public Rectangle Rectangle()
            {
                this.Align();

                int nbits = (int)Ui32(5);
                float xmin = (float)Si32(nbits) / 20.0f;
                float xmax = (float)Si32(nbits) / 20.0f;
                float ymin = (float)Si32(nbits) / 20.0f;
                float ymax = (float)Si32(nbits) / 20.0f;

                return new Rectangle(xmin, ymin, xmax - xmin, ymax - ymin);
            }

            public string String()
            {
                int count;
                return String(out count);
            }

            public string String(out int numBytesRead)
            {
                this.Align();

                StringBuilder result = new StringBuilder();
                numBytesRead = 0;

                if (this.swfVersion >= 6)
                {
                    // See https://tools.ietf.org/html/rfc3629 for UTF8 spec
                    for (;;)
                    {
                        stream.Read(buf, 0, 1);
                        numBytesRead += 1;

                        if (buf[0] == 0)
                        {
                            break;
                        }
                        else if ((buf[0] & 0x80) == 0x00)    // 0xxxxxxx
                        {
                            result.Append((char)buf[0]);
                        }
                        else if ((buf[0] & 0xe0) == 0xc0)    // 110xxxxx 10xxxxxx
                        {
                            stream.Read(buf, 1, 1);
                            numBytesRead += 1;
                            result.Append((char)(((buf[0] & 0x1f) << 6) | (buf[1] & 0x3f)));
                        }
                        else if ((buf[0] & 0xf0) == 0xe0)    // 1110xxxx 10xxxxxx 10xxxxxx
                        {
                            stream.Read(buf, 1, 2);
                            numBytesRead += 2;
                            result.Append((char)(((buf[0] & 0x0f) << 12) | ((buf[1] & 0x3f) << 6) |
                                (buf[2] & 0x3f)));
                        }
                        else if ((buf[0] & 0xf8) == 0xf0)    // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                        {
                            stream.Read(buf, 1, 3);
                            numBytesRead += 3;
                            result.Append((char)(((buf[0] & 0x07) << 18) | ((buf[1] & 0x3f) << 12) |
                                ((buf[2] & 0x3f) << 6) | (buf[3] & 0x3f)));
                        }
                    }
                }
                else
                {
                    for (;;)
                    {
                        stream.Read(buf, 0, 1);
                        numBytesRead += 1;

                        if (buf[0] == 0)
                        {
                            break;
                        }
                        else
                        {
                            result.Append((char)buf[0]);
                        }
                    }
                }

                return result.ToString();
            }

            public string String(int length)
            {
                this.Align();

                StringBuilder result = new StringBuilder();

                if (this.swfVersion >= 6)
                {
                    // See https://tools.ietf.org/html/rfc3629 for UTF8 spec
                    while (length > 0)
                    {
                        stream.Read(buf, 0, 1);
                        length -= 1;

                        if (buf[0] == 0)
                        {
                            break;
                        }
                        else if ((buf[0] & 0x80) == 0x00)    // 0xxxxxxx
                        {
                            result.Append((char)buf[0]);
                        }
                        else if ((buf[0] & 0xe0) == 0xc0)    // 110xxxxx 10xxxxxx
                        {
                            stream.Read(buf, 1, 1);
                            length -= 1;
                            result.Append((char)(((buf[0] & 0x1f) << 6) | (buf[1] & 0x3f)));
                        }
                        else if ((buf[0] & 0xf0) == 0xe0)    // 1110xxxx 10xxxxxx 10xxxxxx
                        {
                            stream.Read(buf, 1, 2);
                            length -= 2;
                            result.Append((char)(((buf[0] & 0x0f) << 12) | ((buf[1] & 0x3f) << 6) |
                                (buf[2] & 0x3f)));
                        }
                        else if ((buf[0] & 0xf8) == 0xf0)    // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
                        {
                            stream.Read(buf, 1, 3);
                            length -= 3;
                            result.Append((char)(((buf[0] & 0x07) << 18) | ((buf[1] & 0x3f) << 12) |
                                ((buf[2] & 0x3f) << 6) | (buf[3] & 0x3f)));
                        }
                    }
                }
                else
                {
                    while (length > 0)
                    {
                        stream.Read(buf, 0, 1);
                        length -= 1;

                        if (buf[0] == 0)
                        {
                            break;
                        }
                        else
                        {
                            result.Append((char)buf[0]);
                        }
                    }
                }

                return result.ToString();
            }

            public byte[] ByteArray(uint count)
            {
                if (count > 0)
                {
                    byte[] buf = new byte[count];
                    stream.Read(buf, 0, (int)count);
                    return buf;
                }
                else
                {
                    return null;
                }
            }

            public void Read(byte[] buf, int offset, int count)
            {
                stream.Read(buf, offset, count);
            }
        }

        private class Edge
        {
            public enum Type { Line, Curve };

            public int x1, y1, cx, cy, x2, y2;

            public Type type;

            public Edge(int x1, int y1, int cx, int cy, int x2, int y2)
            {
                this.type = Edge.Type.Curve;
                this.x1 = x1;
                this.y1 = y1;
                this.cx = cx;
                this.cy = cy;
                this.x2 = x2;
                this.y2 = y2;
            }

            public Edge(int x1, int y1, int x2, int y2)
            {
                this.type = Edge.Type.Line;
                this.x1 = x1;
                this.y1 = y1;
                this.x2 = x2;
                this.y2 = y2;
            }

            public Edge Reverse()
            {
                if (type == Edge.Type.Line)
                {
                    return new Edge(x2, y2, x1, y1);
                }
                else
                {
                    return new Edge(x2, y2, cx, cy, x1, y1);
                }
            }
        }
    }
}
